├── .gitignore ├── LICENSE ├── README.md ├── api.md ├── baby ├── step10_xml_parse_modify_generate.js ├── step11_buildexcel.js ├── step12_buildexcel_data.js ├── step13_buildexcel_data.js ├── step1_open_save.js ├── step2_transplant_style.js ├── step3_read_minimal.js ├── step4_edit_slide.js ├── step5_add_shape.js ├── step6_add_slide.js ├── step7_diff.js ├── step8_frankenstein_copy.js ├── step9_xml_parse_generate.js └── t.js ├── examples └── example.js ├── lib ├── chart.js ├── charts.js ├── defaults.js ├── fragment.js ├── fragments │ ├── Microsoft_Excel_Sheet1.xlsx │ ├── js │ │ ├── chart.js │ │ └── chartframe.js │ └── xml │ │ ├── chart.xml │ │ └── chartframe.xml ├── msexcel-builder.js ├── officechart.js ├── pptx.js ├── presentation.js ├── shape.js ├── shapeProperties.js ├── slide.js ├── util │ └── clone.js └── xmlnode.js ├── package.json ├── test ├── chart.js ├── example.js ├── files │ ├── minimal.pptx │ ├── parts3-a.pptx │ ├── parts3-b.pptx │ └── parts3.pptx ├── synthesize.js └── xmlnode.js └── xml2js.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | lab/ 30 | .idea/ 31 | .DS_Store 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | ========================== 3 | 4 | Version 3, 29 June 2007 5 | 6 | Copyright © 2007 Free Software Foundation, Inc. <> 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this license 9 | document, but changing it is not allowed. 10 | 11 | ## Preamble 12 | 13 | The GNU General Public License is a free, copyleft license for software and other 14 | kinds of works. 15 | 16 | The licenses for most software and other practical works are designed to take away 17 | your freedom to share and change the works. By contrast, the GNU General Public 18 | License is intended to guarantee your freedom to share and change all versions of a 19 | program--to make sure it remains free software for all its users. We, the Free 20 | Software Foundation, use the GNU General Public License for most of our software; it 21 | applies also to any other work released this way by its authors. You can apply it to 22 | your programs, too. 23 | 24 | When we speak of free software, we are referring to freedom, not price. Our General 25 | Public Licenses are designed to make sure that you have the freedom to distribute 26 | copies of free software (and charge for them if you wish), that you receive source 27 | code or can get it if you want it, that you can change the software or use pieces of 28 | it in new free programs, and that you know you can do these things. 29 | 30 | To protect your rights, we need to prevent others from denying you these rights or 31 | asking you to surrender the rights. Therefore, you have certain responsibilities if 32 | you distribute copies of the software, or if you modify it: responsibilities to 33 | respect the freedom of others. 34 | 35 | For example, if you distribute copies of such a program, whether gratis or for a fee, 36 | you must pass on to the recipients the same freedoms that you received. You must make 37 | sure that they, too, receive or can get the source code. And you must show them these 38 | terms so they know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: (1) assert 41 | copyright on the software, and (2) offer you this License giving you legal permission 42 | to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains that there is 45 | no warranty for this free software. For both users' and authors' sake, the GPL 46 | requires that modified versions be marked as changed, so that their problems will not 47 | be attributed erroneously to authors of previous versions. 48 | 49 | Some devices are designed to deny users access to install or run modified versions of 50 | the software inside them, although the manufacturer can do so. This is fundamentally 51 | incompatible with the aim of protecting users' freedom to change the software. The 52 | systematic pattern of such abuse occurs in the area of products for individuals to 53 | use, which is precisely where it is most unacceptable. Therefore, we have designed 54 | this version of the GPL to prohibit the practice for those products. If such problems 55 | arise substantially in other domains, we stand ready to extend this provision to 56 | those domains in future versions of the GPL, as needed to protect the freedom of 57 | users. 58 | 59 | Finally, every program is threatened constantly by software patents. States should 60 | not allow patents to restrict development and use of software on general-purpose 61 | computers, but in those that do, we wish to avoid the special danger that patents 62 | applied to a free program could make it effectively proprietary. To prevent this, the 63 | GPL assures that patents cannot be used to render the program non-free. 64 | 65 | The precise terms and conditions for copying, distribution and modification follow. 66 | 67 | ## TERMS AND CONDITIONS 68 | 69 | ### 0. Definitions. 70 | 71 | “This License” refers to version 3 of the GNU General Public License. 72 | 73 | “Copyright” also means copyright-like laws that apply to other kinds of 74 | works, such as semiconductor masks. 75 | 76 | “The Program” refers to any copyrightable work licensed under this 77 | License. Each licensee is addressed as “you”. “Licensees” and 78 | “recipients” may be individuals or organizations. 79 | 80 | To “modify” a work means to copy from or adapt all or part of the work in 81 | a fashion requiring copyright permission, other than the making of an exact copy. The 82 | resulting work is called a “modified version” of the earlier work or a 83 | work “based on” the earlier work. 84 | 85 | A “covered work” means either the unmodified Program or a work based on 86 | the Program. 87 | 88 | To “propagate” a work means to do anything with it that, without 89 | permission, would make you directly or secondarily liable for infringement under 90 | applicable copyright law, except executing it on a computer or modifying a private 91 | copy. Propagation includes copying, distribution (with or without modification), 92 | making available to the public, and in some countries other activities as well. 93 | 94 | To “convey” a work means any kind of propagation that enables other 95 | parties to make or receive copies. Mere interaction with a user through a computer 96 | network, with no transfer of a copy, is not conveying. 97 | 98 | An interactive user interface displays “Appropriate Legal Notices” to the 99 | extent that it includes a convenient and prominently visible feature that (1) 100 | displays an appropriate copyright notice, and (2) tells the user that there is no 101 | warranty for the work (except to the extent that warranties are provided), that 102 | licensees may convey the work under this License, and how to view a copy of this 103 | License. If the interface presents a list of user commands or options, such as a 104 | menu, a prominent item in the list meets this criterion. 105 | 106 | ### 1. Source Code. 107 | 108 | The “source code” for a work means the preferred form of the work for 109 | making modifications to it. “Object code” means any non-source form of a 110 | work. 111 | 112 | A “Standard Interface” means an interface that either is an official 113 | standard defined by a recognized standards body, or, in the case of interfaces 114 | specified for a particular programming language, one that is widely used among 115 | developers working in that language. 116 | 117 | The “System Libraries” of an executable work include anything, other than 118 | the work as a whole, that (a) is included in the normal form of packaging a Major 119 | Component, but which is not part of that Major Component, and (b) serves only to 120 | enable use of the work with that Major Component, or to implement a Standard 121 | Interface for which an implementation is available to the public in source code form. 122 | A “Major Component”, in this context, means a major essential component 123 | (kernel, window system, and so on) of the specific operating system (if any) on which 124 | the executable work runs, or a compiler used to produce the work, or an object code 125 | interpreter used to run it. 126 | 127 | The “Corresponding Source” for a work in object code form means all the 128 | source code needed to generate, install, and (for an executable work) run the object 129 | code and to modify the work, including scripts to control those activities. However, 130 | it does not include the work's System Libraries, or general-purpose tools or 131 | generally available free programs which are used unmodified in performing those 132 | activities but which are not part of the work. For example, Corresponding Source 133 | includes interface definition files associated with source files for the work, and 134 | the source code for shared libraries and dynamically linked subprograms that the work 135 | is specifically designed to require, such as by intimate data communication or 136 | control flow between those subprograms and other parts of the work. 137 | 138 | The Corresponding Source need not include anything that users can regenerate 139 | automatically from other parts of the Corresponding Source. 140 | 141 | The Corresponding Source for a work in source code form is that same work. 142 | 143 | ### 2. Basic Permissions. 144 | 145 | All rights granted under this License are granted for the term of copyright on the 146 | Program, and are irrevocable provided the stated conditions are met. This License 147 | explicitly affirms your unlimited permission to run the unmodified Program. The 148 | output from running a covered work is covered by this License only if the output, 149 | given its content, constitutes a covered work. This License acknowledges your rights 150 | of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not convey, without 153 | conditions so long as your license otherwise remains in force. You may convey covered 154 | works to others for the sole purpose of having them make modifications exclusively 155 | for you, or provide you with facilities for running those works, provided that you 156 | comply with the terms of this License in conveying all material for which you do not 157 | control copyright. Those thus making or running the covered works for you must do so 158 | exclusively on your behalf, under your direction and control, on terms that prohibit 159 | them from making any copies of your copyrighted material outside their relationship 160 | with you. 161 | 162 | Conveying under any other circumstances is permitted solely under the conditions 163 | stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 164 | 165 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 166 | 167 | No covered work shall be deemed part of an effective technological measure under any 168 | applicable law fulfilling obligations under article 11 of the WIPO copyright treaty 169 | adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention 170 | of such measures. 171 | 172 | When you convey a covered work, you waive any legal power to forbid circumvention of 173 | technological measures to the extent such circumvention is effected by exercising 174 | rights under this License with respect to the covered work, and you disclaim any 175 | intention to limit operation or modification of the work as a means of enforcing, 176 | against the work's users, your or third parties' legal rights to forbid circumvention 177 | of technological measures. 178 | 179 | ### 4. Conveying Verbatim Copies. 180 | 181 | You may convey verbatim copies of the Program's source code as you receive it, in any 182 | medium, provided that you conspicuously and appropriately publish on each copy an 183 | appropriate copyright notice; keep intact all notices stating that this License and 184 | any non-permissive terms added in accord with section 7 apply to the code; keep 185 | intact all notices of the absence of any warranty; and give all recipients a copy of 186 | this License along with the Program. 187 | 188 | You may charge any price or no price for each copy that you convey, and you may offer 189 | support or warranty protection for a fee. 190 | 191 | ### 5. Conveying Modified Source Versions. 192 | 193 | You may convey a work based on the Program, or the modifications to produce it from 194 | the Program, in the form of source code under the terms of section 4, provided that 195 | you also meet all of these conditions: 196 | 197 | * **a)** The work must carry prominent notices stating that you modified it, and giving a 198 | relevant date. 199 | * **b)** The work must carry prominent notices stating that it is released under this 200 | License and any conditions added under section 7. This requirement modifies the 201 | requirement in section 4 to “keep intact all notices”. 202 | * **c)** You must license the entire work, as a whole, under this License to anyone who 203 | comes into possession of a copy. This License will therefore apply, along with any 204 | applicable section 7 additional terms, to the whole of the work, and all its parts, 205 | regardless of how they are packaged. This License gives no permission to license the 206 | work in any other way, but it does not invalidate such permission if you have 207 | separately received it. 208 | * **d)** If the work has interactive user interfaces, each must display Appropriate Legal 209 | Notices; however, if the Program has interactive interfaces that do not display 210 | Appropriate Legal Notices, your work need not make them do so. 211 | 212 | A compilation of a covered work with other separate and independent works, which are 213 | not by their nature extensions of the covered work, and which are not combined with 214 | it such as to form a larger program, in or on a volume of a storage or distribution 215 | medium, is called an “aggregate” if the compilation and its resulting 216 | copyright are not used to limit the access or legal rights of the compilation's users 217 | beyond what the individual works permit. Inclusion of a covered work in an aggregate 218 | does not cause this License to apply to the other parts of the aggregate. 219 | 220 | ### 6. Conveying Non-Source Forms. 221 | 222 | You may convey a covered work in object code form under the terms of sections 4 and 223 | 5, provided that you also convey the machine-readable Corresponding Source under the 224 | terms of this License, in one of these ways: 225 | 226 | * **a)** Convey the object code in, or embodied in, a physical product (including a 227 | physical distribution medium), accompanied by the Corresponding Source fixed on a 228 | durable physical medium customarily used for software interchange. 229 | * **b)** Convey the object code in, or embodied in, a physical product (including a 230 | physical distribution medium), accompanied by a written offer, valid for at least 231 | three years and valid for as long as you offer spare parts or customer support for 232 | that product model, to give anyone who possesses the object code either (1) a copy of 233 | the Corresponding Source for all the software in the product that is covered by this 234 | License, on a durable physical medium customarily used for software interchange, for 235 | a price no more than your reasonable cost of physically performing this conveying of 236 | source, or (2) access to copy the Corresponding Source from a network server at no 237 | charge. 238 | * **c)** Convey individual copies of the object code with a copy of the written offer to 239 | provide the Corresponding Source. This alternative is allowed only occasionally and 240 | noncommercially, and only if you received the object code with such an offer, in 241 | accord with subsection 6b. 242 | * **d)** Convey the object code by offering access from a designated place (gratis or for 243 | a charge), and offer equivalent access to the Corresponding Source in the same way 244 | through the same place at no further charge. You need not require recipients to copy 245 | the Corresponding Source along with the object code. If the place to copy the object 246 | code is a network server, the Corresponding Source may be on a different server 247 | (operated by you or a third party) that supports equivalent copying facilities, 248 | provided you maintain clear directions next to the object code saying where to find 249 | the Corresponding Source. Regardless of what server hosts the Corresponding Source, 250 | you remain obligated to ensure that it is available for as long as needed to satisfy 251 | these requirements. 252 | * **e)** Convey the object code using peer-to-peer transmission, provided you inform 253 | other peers where the object code and Corresponding Source of the work are being 254 | offered to the general public at no charge under subsection 6d. 255 | 256 | A separable portion of the object code, whose source code is excluded from the 257 | Corresponding Source as a System Library, need not be included in conveying the 258 | object code work. 259 | 260 | A “User Product” is either (1) a “consumer product”, which 261 | means any tangible personal property which is normally used for personal, family, or 262 | household purposes, or (2) anything designed or sold for incorporation into a 263 | dwelling. In determining whether a product is a consumer product, doubtful cases 264 | shall be resolved in favor of coverage. For a particular product received by a 265 | particular user, “normally used” refers to a typical or common use of 266 | that class of product, regardless of the status of the particular user or of the way 267 | in which the particular user actually uses, or expects or is expected to use, the 268 | product. A product is a consumer product regardless of whether the product has 269 | substantial commercial, industrial or non-consumer uses, unless such uses represent 270 | the only significant mode of use of the product. 271 | 272 | “Installation Information” for a User Product means any methods, 273 | procedures, authorization keys, or other information required to install and execute 274 | modified versions of a covered work in that User Product from a modified version of 275 | its Corresponding Source. The information must suffice to ensure that the continued 276 | functioning of the modified object code is in no case prevented or interfered with 277 | solely because modification has been made. 278 | 279 | If you convey an object code work under this section in, or with, or specifically for 280 | use in, a User Product, and the conveying occurs as part of a transaction in which 281 | the right of possession and use of the User Product is transferred to the recipient 282 | in perpetuity or for a fixed term (regardless of how the transaction is 283 | characterized), the Corresponding Source conveyed under this section must be 284 | accompanied by the Installation Information. But this requirement does not apply if 285 | neither you nor any third party retains the ability to install modified object code 286 | on the User Product (for example, the work has been installed in ROM). 287 | 288 | The requirement to provide Installation Information does not include a requirement to 289 | continue to provide support service, warranty, or updates for a work that has been 290 | modified or installed by the recipient, or for the User Product in which it has been 291 | modified or installed. Access to a network may be denied when the modification itself 292 | materially and adversely affects the operation of the network or violates the rules 293 | and protocols for communication across the network. 294 | 295 | Corresponding Source conveyed, and Installation Information provided, in accord with 296 | this section must be in a format that is publicly documented (and with an 297 | implementation available to the public in source code form), and must require no 298 | special password or key for unpacking, reading or copying. 299 | 300 | ### 7. Additional Terms. 301 | 302 | “Additional permissions” are terms that supplement the terms of this 303 | License by making exceptions from one or more of its conditions. Additional 304 | permissions that are applicable to the entire Program shall be treated as though they 305 | were included in this License, to the extent that they are valid under applicable 306 | law. If additional permissions apply only to part of the Program, that part may be 307 | used separately under those permissions, but the entire Program remains governed by 308 | this License without regard to the additional permissions. 309 | 310 | When you convey a copy of a covered work, you may at your option remove any 311 | additional permissions from that copy, or from any part of it. (Additional 312 | permissions may be written to require their own removal in certain cases when you 313 | modify the work.) You may place additional permissions on material, added by you to a 314 | covered work, for which you have or can give appropriate copyright permission. 315 | 316 | Notwithstanding any other provision of this License, for material you add to a 317 | covered work, you may (if authorized by the copyright holders of that material) 318 | supplement the terms of this License with terms: 319 | 320 | * **a)** Disclaiming warranty or limiting liability differently from the terms of 321 | sections 15 and 16 of this License; or 322 | * **b)** Requiring preservation of specified reasonable legal notices or author 323 | attributions in that material or in the Appropriate Legal Notices displayed by works 324 | containing it; or 325 | * **c)** Prohibiting misrepresentation of the origin of that material, or requiring that 326 | modified versions of such material be marked in reasonable ways as different from the 327 | original version; or 328 | * **d)** Limiting the use for publicity purposes of names of licensors or authors of the 329 | material; or 330 | * **e)** Declining to grant rights under trademark law for use of some trade names, 331 | trademarks, or service marks; or 332 | * **f)** Requiring indemnification of licensors and authors of that material by anyone 333 | who conveys the material (or modified versions of it) with contractual assumptions of 334 | liability to the recipient, for any liability that these contractual assumptions 335 | directly impose on those licensors and authors. 336 | 337 | All other non-permissive additional terms are considered “further 338 | restrictions” within the meaning of section 10. If the Program as you received 339 | it, or any part of it, contains a notice stating that it is governed by this License 340 | along with a term that is a further restriction, you may remove that term. If a 341 | license document contains a further restriction but permits relicensing or conveying 342 | under this License, you may add to a covered work material governed by the terms of 343 | that license document, provided that the further restriction does not survive such 344 | relicensing or conveying. 345 | 346 | If you add terms to a covered work in accord with this section, you must place, in 347 | the relevant source files, a statement of the additional terms that apply to those 348 | files, or a notice indicating where to find the applicable terms. 349 | 350 | Additional terms, permissive or non-permissive, may be stated in the form of a 351 | separately written license, or stated as exceptions; the above requirements apply 352 | either way. 353 | 354 | ### 8. Termination. 355 | 356 | You may not propagate or modify a covered work except as expressly provided under 357 | this License. Any attempt otherwise to propagate or modify it is void, and will 358 | automatically terminate your rights under this License (including any patent licenses 359 | granted under the third paragraph of section 11). 360 | 361 | However, if you cease all violation of this License, then your license from a 362 | particular copyright holder is reinstated (a) provisionally, unless and until the 363 | copyright holder explicitly and finally terminates your license, and (b) permanently, 364 | if the copyright holder fails to notify you of the violation by some reasonable means 365 | prior to 60 days after the cessation. 366 | 367 | Moreover, your license from a particular copyright holder is reinstated permanently 368 | if the copyright holder notifies you of the violation by some reasonable means, this 369 | is the first time you have received notice of violation of this License (for any 370 | work) from that copyright holder, and you cure the violation prior to 30 days after 371 | your receipt of the notice. 372 | 373 | Termination of your rights under this section does not terminate the licenses of 374 | parties who have received copies or rights from you under this License. If your 375 | rights have been terminated and not permanently reinstated, you do not qualify to 376 | receive new licenses for the same material under section 10. 377 | 378 | ### 9. Acceptance Not Required for Having Copies. 379 | 380 | You are not required to accept this License in order to receive or run a copy of the 381 | Program. Ancillary propagation of a covered work occurring solely as a consequence of 382 | using peer-to-peer transmission to receive a copy likewise does not require 383 | acceptance. However, nothing other than this License grants you permission to 384 | propagate or modify any covered work. These actions infringe copyright if you do not 385 | accept this License. Therefore, by modifying or propagating a covered work, you 386 | indicate your acceptance of this License to do so. 387 | 388 | ### 10. Automatic Licensing of Downstream Recipients. 389 | 390 | Each time you convey a covered work, the recipient automatically receives a license 391 | from the original licensors, to run, modify and propagate that work, subject to this 392 | License. You are not responsible for enforcing compliance by third parties with this 393 | License. 394 | 395 | An “entity transaction” is a transaction transferring control of an 396 | organization, or substantially all assets of one, or subdividing an organization, or 397 | merging organizations. If propagation of a covered work results from an entity 398 | transaction, each party to that transaction who receives a copy of the work also 399 | receives whatever licenses to the work the party's predecessor in interest had or 400 | could give under the previous paragraph, plus a right to possession of the 401 | Corresponding Source of the work from the predecessor in interest, if the predecessor 402 | has it or can get it with reasonable efforts. 403 | 404 | You may not impose any further restrictions on the exercise of the rights granted or 405 | affirmed under this License. For example, you may not impose a license fee, royalty, 406 | or other charge for exercise of rights granted under this License, and you may not 407 | initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging 408 | that any patent claim is infringed by making, using, selling, offering for sale, or 409 | importing the Program or any portion of it. 410 | 411 | ### 11. Patents. 412 | 413 | A “contributor” is a copyright holder who authorizes use under this 414 | License of the Program or a work on which the Program is based. The work thus 415 | licensed is called the contributor's “contributor version”. 416 | 417 | A contributor's “essential patent claims” are all patent claims owned or 418 | controlled by the contributor, whether already acquired or hereafter acquired, that 419 | would be infringed by some manner, permitted by this License, of making, using, or 420 | selling its contributor version, but do not include claims that would be infringed 421 | only as a consequence of further modification of the contributor version. For 422 | purposes of this definition, “control” includes the right to grant patent 423 | sublicenses in a manner consistent with the requirements of this License. 424 | 425 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent license 426 | under the contributor's essential patent claims, to make, use, sell, offer for sale, 427 | import and otherwise run, modify and propagate the contents of its contributor 428 | version. 429 | 430 | In the following three paragraphs, a “patent license” is any express 431 | agreement or commitment, however denominated, not to enforce a patent (such as an 432 | express permission to practice a patent or covenant not to sue for patent 433 | infringement). To “grant” such a patent license to a party means to make 434 | such an agreement or commitment not to enforce a patent against the party. 435 | 436 | If you convey a covered work, knowingly relying on a patent license, and the 437 | Corresponding Source of the work is not available for anyone to copy, free of charge 438 | and under the terms of this License, through a publicly available network server or 439 | other readily accessible means, then you must either (1) cause the Corresponding 440 | Source to be so available, or (2) arrange to deprive yourself of the benefit of the 441 | patent license for this particular work, or (3) arrange, in a manner consistent with 442 | the requirements of this License, to extend the patent license to downstream 443 | recipients. “Knowingly relying” means you have actual knowledge that, but 444 | for the patent license, your conveying the covered work in a country, or your 445 | recipient's use of the covered work in a country, would infringe one or more 446 | identifiable patents in that country that you have reason to believe are valid. 447 | 448 | If, pursuant to or in connection with a single transaction or arrangement, you 449 | convey, or propagate by procuring conveyance of, a covered work, and grant a patent 450 | license to some of the parties receiving the covered work authorizing them to use, 451 | propagate, modify or convey a specific copy of the covered work, then the patent 452 | license you grant is automatically extended to all recipients of the covered work and 453 | works based on it. 454 | 455 | A patent license is “discriminatory” if it does not include within the 456 | scope of its coverage, prohibits the exercise of, or is conditioned on the 457 | non-exercise of one or more of the rights that are specifically granted under this 458 | License. You may not convey a covered work if you are a party to an arrangement with 459 | a third party that is in the business of distributing software, under which you make 460 | payment to the third party based on the extent of your activity of conveying the 461 | work, and under which the third party grants, to any of the parties who would receive 462 | the covered work from you, a discriminatory patent license (a) in connection with 463 | copies of the covered work conveyed by you (or copies made from those copies), or (b) 464 | primarily for and in connection with specific products or compilations that contain 465 | the covered work, unless you entered into that arrangement, or that patent license 466 | was granted, prior to 28 March 2007. 467 | 468 | Nothing in this License shall be construed as excluding or limiting any implied 469 | license or other defenses to infringement that may otherwise be available to you 470 | under applicable patent law. 471 | 472 | ### 12. No Surrender of Others' Freedom. 473 | 474 | If conditions are imposed on you (whether by court order, agreement or otherwise) 475 | that contradict the conditions of this License, they do not excuse you from the 476 | conditions of this License. If you cannot convey a covered work so as to satisfy 477 | simultaneously your obligations under this License and any other pertinent 478 | obligations, then as a consequence you may not convey it at all. For example, if you 479 | agree to terms that obligate you to collect a royalty for further conveying from 480 | those to whom you convey the Program, the only way you could satisfy both those terms 481 | and this License would be to refrain entirely from conveying the Program. 482 | 483 | ### 13. Use with the GNU Affero General Public License. 484 | 485 | Notwithstanding any other provision of this License, you have permission to link or 486 | combine any covered work with a work licensed under version 3 of the GNU Affero 487 | General Public License into a single combined work, and to convey the resulting work. 488 | The terms of this License will continue to apply to the part which is the covered 489 | work, but the special requirements of the GNU Affero General Public License, section 490 | 13, concerning interaction through a network will apply to the combination as such. 491 | 492 | ### 14. Revised Versions of this License. 493 | 494 | The Free Software Foundation may publish revised and/or new versions of the GNU 495 | General Public License from time to time. Such new versions will be similar in spirit 496 | to the present version, but may differ in detail to address new problems or concerns. 497 | 498 | Each version is given a distinguishing version number. If the Program specifies that 499 | a certain numbered version of the GNU General Public License “or any later 500 | version” applies to it, you have the option of following the terms and 501 | conditions either of that numbered version or of any later version published by the 502 | Free Software Foundation. If the Program does not specify a version number of the GNU 503 | General Public License, you may choose any version ever published by the Free 504 | Software Foundation. 505 | 506 | If the Program specifies that a proxy can decide which future versions of the GNU 507 | General Public License can be used, that proxy's public statement of acceptance of a 508 | version permanently authorizes you to choose that version for the Program. 509 | 510 | Later license versions may give you additional or different permissions. However, no 511 | additional obligations are imposed on any author or copyright holder as a result of 512 | your choosing to follow a later version. 513 | 514 | ### 15. Disclaimer of Warranty. 515 | 516 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 517 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 518 | PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER 519 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 520 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE 521 | QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 522 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 523 | 524 | ### 16. Limitation of Liability. 525 | 526 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY 527 | COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS 528 | PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, 529 | INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 530 | PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE 531 | OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE 532 | WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 533 | POSSIBILITY OF SUCH DAMAGES. 534 | 535 | ### 17. Interpretation of Sections 15 and 16. 536 | 537 | If the disclaimer of warranty and limitation of liability provided above cannot be 538 | given local legal effect according to their terms, reviewing courts shall apply local 539 | law that most closely approximates an absolute waiver of all civil liability in 540 | connection with the Program, unless a warranty or assumption of liability accompanies 541 | a copy of the Program in return for a fee. 542 | 543 | END OF TERMS AND CONDITIONS 544 | 545 | ## How to Apply These Terms to Your New Programs 546 | 547 | If you develop a new program, and you want it to be of the greatest possible use to 548 | the public, the best way to achieve this is to make it free software which everyone 549 | can redistribute and change under these terms. 550 | 551 | To do so, attach the following notices to the program. It is safest to attach them 552 | to the start of each source file to most effectively state the exclusion of warranty; 553 | and each file should have at least the “copyright” line and a pointer to 554 | where the full notice is found. 555 | 556 | 557 | Copyright (C) 558 | 559 | This program is free software: you can redistribute it and/or modify 560 | it under the terms of the GNU General Public License as published by 561 | the Free Software Foundation, either version 3 of the License, or 562 | (at your option) any later version. 563 | 564 | This program is distributed in the hope that it will be useful, 565 | but WITHOUT ANY WARRANTY; without even the implied warranty of 566 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 567 | GNU General Public License for more details. 568 | 569 | You should have received a copy of the GNU General Public License 570 | along with this program. If not, see . 571 | 572 | Also add information on how to contact you by electronic and paper mail. 573 | 574 | If the program does terminal interaction, make it output a short notice like this 575 | when it starts in an interactive mode: 576 | 577 | Copyright (C) 578 | This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. 579 | This is free software, and you are welcome to redistribute it 580 | under certain conditions; type 'show c' for details. 581 | 582 | The hypothetical commands 'show w' and 'show c' should show the appropriate parts of 583 | the General Public License. Of course, your program's commands might be different; 584 | for a GUI interface, you would use an “about box”. 585 | 586 | You should also get your employer (if you work as a programmer) or school, if any, to 587 | sign a “copyright disclaimer” for the program, if necessary. For more 588 | information on this, and how to apply and follow the GNU GPL, see 589 | <>. 590 | 591 | The GNU General Public License does not permit incorporating your program into 592 | proprietary programs. If your program is a subroutine library, you may consider it 593 | more useful to permit linking proprietary applications with the library. If this is 594 | what you want to do, use the GNU Lesser General Public License instead of this 595 | License. But first, please read 596 | <>. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-pptx 2 | Pure Javascript reader/writer/editor for PowerPoint, for use in Node.js or the browser. 3 | 4 | 5 | # Design goals 6 | * Read/edit/author PowerPoint .pptx files 7 | * Pure Javascript with clean IP 8 | * Run in browser and/or Node.js 9 | * Friendly API for basic tasks, like text, shapes, charts, tables 10 | * Access to raw XML for when you need to be very specific 11 | * Rigorous testing 12 | 13 | 14 | # Current status 15 | Early in development. It can currently: 16 | * read an existing PPTX file 17 | * retain all existing content 18 | * add slides, shapes, and charts 19 | * save as a PPTX file 20 | * basic unit tests 21 | 22 | What it cannot yet do is: 23 | * Programmatically retrieve / query / edit existing slides 24 | * Generate themes, layouts, masters, animations, etc. 25 | 26 | # License 27 | GNU General Public License (GPL) 28 | 29 | # Install 30 | 31 | In node.js 32 | ``` 33 | npm install protobi/js-pptx 34 | ``` 35 | 36 | In the browser: **(Not yet implemented)** 37 | ``` 38 | 39 | ``` 40 | 41 | # Dependencies 42 | * [xml2js](https://github.com/nfarina/xmldoc) 43 | * [async](https://github.com/caolan/async) 44 | * [jszip](https://stuk.github.io/jszip) 45 | 46 | # Usage 47 | 48 | ```js 49 | var PPTX = require('../lib/pptx'); 50 | var fs = require('fs'); 51 | 52 | var INFILE = './test/files/parts3.pptx'; 53 | var OUTFILE = './test/files/parts3-a.pptx'; 54 | 55 | fs.readFile(INFILE, function (err, data) { 56 | if (err) throw err; 57 | 58 | var pptx = new PPTX.Presentation(); 59 | pptx.load(data, function (err) { 60 | 61 | var slide1 = pptx.getSlide('slide1'); 62 | var shapes = slide1.getShapes(); 63 | 64 | var shapes = slide1.getShapes() 65 | shapes[3] 66 | .text("Now it's a trapezoid") 67 | .shapeProperties() 68 | .x(PPTX.emu.inch(1)) 69 | .y(PPTX.emu.inch(1)) 70 | .cx(PPTX.emu.inch(2)) 71 | .cy(PPTX.emu.inch(0.75)) 72 | .prstGeom('trapezoid'); 73 | }); 74 | }); 75 | }); 76 | 77 | ``` 78 | 79 | # Inspiration / Motivation 80 | Inspired by [officegen](https://github.com/ZivBarber/officegen), 81 | which creates pptx with text/shapes/images/tables/charts wonderfully (but does not read existing PPT files). 82 | 83 | Also inspired by [js-xlsx](https://github.com/SheetJS/js-xlsx) 84 | which reads/writes XLSX/XLS/XLSB, works in the browser and Node.js, and has an incredibly 85 | thorough test suite (but does not read or write PowerPoint). 86 | 87 | Motivated by desire to read and modify existing presentations, to inherit their themes, layouts and possibly content, 88 | and work in the browser if possible. 89 | 90 | https://github.com/protobi/js-pptx/wiki/API 91 | 92 | # Design Philosophy 93 | The design concept is to represent the Office document at two levels of abstraction: 94 | * **Raw XML** The actual complete OpenXML representation, in all its detail 95 | * **Conceptual classes** Simple Javascript classes that provide a convenient API 96 | 97 | The conceptual classes provides a clear simple way to do common tasks, e.g. `Presentation().addSlide().addChart(data)`. 98 | 99 | The raw API provides a way to do anything that the OpenXML allows, even if it's not yet in the conceptual classes, e.g. 100 | e.g. `Presentation.getSlide(3).getShape(4).get('a:prstGeom').attr('prst', 'trapezoid')` 101 | 102 | 103 | This solves a major dilemma in existing projects, which have many issue reports like "Please add this crucial feature to the API". 104 | By being able to access the raw XML, all the features in OpenXML are available, while we make many of them more convenient. 105 | 106 | The technical approach here uses: 107 | * `JSZip` to unzip an existing `.pptx` file and zip it back, 108 | * `xml2js` to convert the XML to Javascript and back to XML. 109 | 110 | Converting to Javascript allows the content to be manipulated programmatically. For each major entity, a Javascript class is created, 111 | such as: 112 | * PPTX.Presentation 113 | * PPTX.Slide 114 | * PPTX.Shape 115 | * PPTX.spPr // ShapeProperties 116 | * etc. 117 | 118 | These classes allow properties to be set, and chained in a manner similar to d3 or jQuery. 119 | The Javascript classes provide syntactic sugar, as a convenient way to query and modify the presentation. 120 | 121 | But we can't possibly create a Javascript class that covers every entity and option defined in OpenXML. 122 | So each of these classes exposes the XML-to-Javascript object as a property `.content`, giving you theoretically 123 | direct access to anything in the OpenXML standard, enabling you to take over 124 | whenever the pre-defined features don't yet cover your particular use case. 125 | 126 | It's up to you of course, to make sure that those changes convert to valid XML. Debugging PPTX is a pain. 127 | 128 | Right now, this uses English names for high-level constructs (e.g. `Presentation` and `Slide`), 129 | but for lower level constructs uses names that directly mirror the OpenXML tagNames (e.g. `spPr` for ShapeProperties). 130 | 131 | The challenge is it'll be a lot easier to extend the library if we follow the OpenXML tag names, 132 | but the OpenXML tag names are so cryptic that they don't make great names for a Javascript library. 133 | 134 | So we default to using the English name is used when returning objects even if the object has a cryptic class name, e.g.: 135 | * `Slide.getShapes()` returns an array of `Shape` objects and 136 | * `Shape.shapeProperties()` returns an `spPr` object. 137 | 138 | Ideally would be consistent, and am working out which way to go. Advice is welcome! 139 | 140 | This library currently assumes it's starting from an existing presentation, and doesn't (yet) create one from scratch. 141 | This allows you to use existing themes, styles and layouts. 142 | 143 | 144 | # License 145 | GNU General Public License (GPL) 146 | 147 | # Install 148 | 149 | In node.js 150 | ``` 151 | npm install protobi/js-pptx 152 | ``` 153 | 154 | In the browser: 155 | ``` 156 | // will use browserify but right now not yet implemented 157 | ``` 158 | 159 | # Dependencies 160 | * [xml2js](https://github.com/nfarina/xmldoc) 161 | * [async](https://github.com/caolan/async) 162 | * [jszip](https://stuk.github.io/jszip) 163 | 164 | # Usage 165 | 166 | ```js 167 | "use strict"; 168 | 169 | var fs = require("fs"); 170 | var PPTX = require('..'); 171 | 172 | 173 | var INFILE = './test/files/minimal.pptx'; // a blank PPTX file with my layouts, themes, masters. 174 | var OUTFILE = '/tmp/example.pptx'; 175 | 176 | fs.readFile(INFILE, function (err, data) { 177 | if (err) throw err; 178 | var pptx = new PPTX.Presentation(); 179 | pptx.load(data, function (err) { 180 | var slide1 = pptx.getSlide('slide1'); 181 | 182 | var slide2 = pptx.addSlide("slideLayout3"); // section divider 183 | var slide3 = pptx.addSlide("slideLayout6"); // title only 184 | 185 | 186 | var triangle = slide1.addShape() 187 | .text("Triangle") 188 | .shapeProperties() 189 | .x(PPTX.emu.inch(2)) 190 | .y(PPTX.emu.inch(2)) 191 | .cx(PPTX.emu.inch(2)) 192 | .cy(PPTX.emu.inch(2)) 193 | .prstGeom('triangle'); 194 | 195 | var triangle = slide1.addShape() 196 | .text("Ellipse") 197 | .shapeProperties() 198 | .x(PPTX.emu.inch(4)) 199 | .y(PPTX.emu.inch(4)) 200 | .cx(PPTX.emu.inch(2)) 201 | .cy(PPTX.emu.inch(1)) 202 | .prstGeom('ellipse'); 203 | 204 | for (var i = 0; i < 20; i++) { 205 | slide2.addShape() 206 | .text("" + i) 207 | .shapeProperties() 208 | .x(PPTX.emu.inch((Math.random() * 10))) 209 | .y(PPTX.emu.inch((Math.random() * 6))) 210 | .cx(PPTX.emu.inch(1)) 211 | .cy(PPTX.emu.inch(1)) 212 | .prstGeom('ellipse'); 213 | } 214 | 215 | slide1.getShapes()[3] 216 | .text("Now it's a trapezoid") 217 | .shapeProperties() 218 | .x(PPTX.emu.inch(1)) 219 | .y(PPTX.emu.inch(1)) 220 | .cx(PPTX.emu.inch(2)) 221 | .cy(PPTX.emu.inch(0.75)) 222 | .prstGeom('trapezoid'); 223 | 224 | var chart = slide3.addChart(barChart, function (err, chart) { 225 | 226 | fs.writeFile(OUTFILE, pptx.toBuffer(), function (err) { 227 | if (err) throw err; 228 | console.log("open " + OUTFILE) 229 | }); 230 | }); 231 | }); 232 | }); 233 | 234 | var barChart = { 235 | title: 'Sample bar chart', 236 | renderType: 'bar', 237 | data: [ 238 | { 239 | name: 'Series 1', 240 | labels: ['Category 1', 'Category 2', 'Category 3', 'Category 4'], 241 | values: [4.3, 2.5, 3.5, 4.5] 242 | }, 243 | { 244 | name: 'Series 2', 245 | labels: ['Category 1', 'Category 2', 'Category 3', 'Category 4'], 246 | values: [2.4, 4.4, 1.8, 2.8] 247 | }, 248 | { 249 | name: 'Series 3', 250 | labels: ['Category 1', 'Category 2', 'Category 3', 'Category 4'], 251 | values: [2.0, 2.0, 3.0, 5.0] 252 | } 253 | ] 254 | } 255 | ``` 256 | 257 | # Next steps 258 | * Browserify and test in browser 259 | * Publish to bower 260 | * Add tables 261 | * Add images 262 | * Set presentation properties 263 | * Set theme 264 | * Set layouts 265 | * Set masters 266 | 267 | # Contribute 268 | 269 | ###Test: 270 | `npm test` 271 | 272 | ###Build: 273 | `npm run build` 274 | 275 | ###Minify: 276 | `npm run minify` 277 | 278 | ###All: 279 | `npm run all` 280 | 281 | -------------------------------------------------------------------------------- /api.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/won21kr/js-pptx/7aed5fb3cc88502a58993603936d71eea603bbfc/api.md -------------------------------------------------------------------------------- /baby/step10_xml_parse_modify_generate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require("fs"); 4 | var PPTX = require('../lib/pptx'); 5 | var Query = require('query'); 6 | 7 | Array.prototype.get = function (filter) { 8 | return Query.query(this, filter); 9 | } 10 | 11 | //console.log([{a: 1}, {a:2}, {a:3}].query({a: { $gt: 1}})) 12 | 13 | 14 | var INFILE = './lab/parts3/parts3.pptx'; 15 | var OUTFILE = './lab/parts2/parts2.pptx'; 16 | 17 | 18 | fs.readFile(INFILE, function (err, data) { 19 | if (err) throw err; 20 | var pptx = new PPTX.Presentation(); 21 | pptx.load(data, function (err) { 22 | 23 | 24 | var slide1 = pptx.getSlide('slide1'); 25 | var shapes = slide1.getShapes(); 26 | console.log(shapes[3].text()) 27 | 28 | console.log(JSON.stringify(shapes[3].shapeProperties().toJSON(),null,4)) 29 | 30 | shapes[3].shapeProperties().x(PPTX.emu.inch(1)) 31 | shapes[3].shapeProperties().y(PPTX.emu.inch(1)) 32 | 33 | shapes[3].shapeProperties().cx(PPTX.emu.inch(2)) 34 | shapes[3].shapeProperties().cy(PPTX.emu.inch(0.75)) 35 | 36 | shapes[3].shapeProperties().prstGeom('trapezoid'); 37 | 38 | console.log(JSON.stringify(shapes[3].shapeProperties().toJSON(),null,4)) 39 | 40 | //fs.writeFile('./lab/parts2/parts2.json', JSON.stringify(pptx, null,4), 'utf8'); 41 | fs.writeFile(OUTFILE, pptx.toBuffer(), function (err) { 42 | if (err) throw err; 43 | }); 44 | }); 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /baby/step11_buildexcel.js: -------------------------------------------------------------------------------- 1 | var excelbuilder = require('msexcel-builder'); 2 | 3 | // Create a new workbook file in current working-path 4 | var workbook = excelbuilder.createWorkbook('./', 'sample.xlsx') 5 | 6 | // Create a new worksheet with 10 columns and 12 rows 7 | var sheet1 = workbook.createSheet('sheet1', 10, 12); 8 | 9 | // Fill some data 10 | sheet1.set(1, 1, 'I am title'); 11 | for (var i = 2; i < 5; i++) 12 | sheet1.set(i, 1, 'test'+i); 13 | 14 | // Save it 15 | workbook.save(function(err){ 16 | if (err) 17 | workbook.cancel(); 18 | else 19 | console.log('congratulations, your workbook created'); 20 | }); -------------------------------------------------------------------------------- /baby/step12_buildexcel_data.js: -------------------------------------------------------------------------------- 1 | var excelbuilder = require('msexcel-builder'); 2 | 3 | // Create a new workbook file in current working-path 4 | 5 | 6 | var old = function(data, callback) { 7 | 8 | 9 | // Create a new worksheet with 10 columns and 12 rows 10 | var workbook = excelbuilder.createWorkbook('./', 'sample.xlsx') 11 | var sheet1 = workbook.createSheet('sheet1', 10, 12); 12 | 13 | // Fill some data 14 | sheet1.set(1, 1, 'I am title'); 15 | for (var i = 2; i < 5; i++) 16 | sheet1.set(i, 1, 'test'+i); 17 | 18 | // Save it 19 | workbook.save(callback); 20 | }; 21 | 22 | 23 | var GLOBAL_CHART_COUNT = 0; 24 | createWorkbook = function(data, callback) { 25 | GLOBAL_CHART_COUNT += 1; 26 | var tmpExcelFile = 'sample'+GLOBAL_CHART_COUNT+'.xlsx'; 27 | 28 | 29 | // First, generate a temporatory excel file for storing the chart's data 30 | var workbook = excelbuilder.createWorkbook('./', tmpExcelFile); 31 | 32 | // Create a new worksheet with 10 columns and 12 rows 33 | // number of columns: data['data'].length+1 -> equaly number of series 34 | // number of rows: data['data'][0].values.length+1 35 | var sheet1 = workbook.createSheet('Sheet1', data['data'].length+1, data['data'][0].values.length+1); 36 | var headerrow = 1; 37 | console.log("STARTED WORKBOOK..."); 38 | 39 | // write header using serie name 40 | for( var j=0; j < data['data'].length; j++ ) { 41 | sheet1.set(j+2, headerrow, data['data'][j].name); 42 | } 43 | 44 | // write category column in the first column 45 | for( var j=0; j < data['data'][0].labels.length; j++ ) { 46 | sheet1.set(1, j+2, data['data'][0].labels[j]); 47 | } 48 | 49 | // for each serie, write out values in its row 50 | for (var i = 0; i < data['data'].length; i++) { 51 | for( var j=0; j < data['data'][i].values.length; j++ ) 52 | { 53 | // col i+2 54 | // row j+1 55 | sheet1.set(i+2, j+2, data['data'][i].values[j]); 56 | } 57 | } 58 | console.log("FILLING WORKBOOK..."); 59 | // Fill some data 60 | // Save it 61 | 62 | 63 | console.log("SAVING WORKBOOK..."); 64 | workbook.save(function(err){ 65 | if (err) { 66 | workbook.cancel(); 67 | callback(err); 68 | } 69 | else { 70 | console.log("SAVED: " + workbook.length); 71 | callback(null, workbook); 72 | } 73 | }); 74 | }; 75 | 76 | 77 | var barChart = { 78 | title: 'Sample bar chart', 79 | renderType: 'bar', 80 | xmlOptions: { 81 | "c:title": { 82 | "c:tx": { 83 | "c:rich": { 84 | "a:p": { 85 | "a:r": { 86 | "a:t": "Override title via XML" 87 | } 88 | } 89 | } 90 | } 91 | } 92 | }, 93 | data: [ 94 | { 95 | name: 'europe', 96 | labels: ['Y2003', 'Y2004', 'Y2005'], 97 | values: [2.5, 2.6, 2.8], 98 | color: 'ff0000' 99 | }, 100 | { 101 | name: 'namerica', 102 | labels: ['Y2003', 'Y2004', 'Y2005'], 103 | values: [2.5, 2.7, 2.9], 104 | color: '00ff00' 105 | }, 106 | { 107 | name: 'asia', 108 | labels: ['Y2003', 'Y2004', 'Y2005'], 109 | values: [2.1, 2.2, 2.4], 110 | color: '0000ff' 111 | }, 112 | { 113 | name: 'lamerica', 114 | labels: ['Y2003', 'Y2004', 'Y2005'], 115 | values: [0.3, 0.3, 0.3], 116 | color: 'ffff00' 117 | }, 118 | { 119 | name: 'meast', 120 | labels: ['Y2003', 'Y2004', 'Y2005'], 121 | values: [0.2, 0.3, 0.3], 122 | color: 'ff00ff' 123 | }, 124 | { 125 | name: 'africa', 126 | labels: ['Y2003', 'Y2004', 'Y2005'], 127 | values: [0.1, 0.1, 0.1], 128 | color: '00ffff' 129 | } 130 | 131 | ] 132 | }; 133 | 134 | createWorkbook(barChart, function(err, workbook) {console.log("ALL DONE"); }); 135 | 136 | //old(barChart, function(err, workbook) {console.log("ALL DONE"); }); -------------------------------------------------------------------------------- /baby/step13_buildexcel_data.js: -------------------------------------------------------------------------------- 1 | var excelbuilder = require('msexcel-builder'); 2 | 3 | // Create a new workbook file in current working-path 4 | 5 | 6 | var old = function(data, callback) { 7 | 8 | 9 | // Create a new worksheet with 10 columns and 12 rows 10 | var workbook = excelbuilder.createWorkbook('./', 'sample.xlsx') 11 | var sheet1 = workbook.createSheet('sheet1', 10, 12); 12 | 13 | // Fill some data 14 | sheet1.set(1, 1, 'I am title'); 15 | for (var i = 2; i < 5; i++) 16 | sheet1.set(i, 1, 'test'+i); 17 | 18 | // Save it 19 | workbook.save(callback); 20 | }; 21 | 22 | 23 | var GLOBAL_CHART_COUNT = 0; 24 | createWorkbook = function(data, callback) { 25 | GLOBAL_CHART_COUNT += 1; 26 | var tmpExcelFile = 'sample'+GLOBAL_CHART_COUNT+'.xlsx'; 27 | 28 | 29 | // First, generate a temporatory excel file for storing the chart's data 30 | var workbook = excelbuilder.createWorkbook('./', tmpExcelFile); 31 | 32 | // Create a new worksheet with 10 columns and 12 rows 33 | // number of columns: data['data'].length+1 -> equaly number of series 34 | // number of rows: data['data'][0].values.length+1 35 | var sheet1 = workbook.createSheet('Sheet1', data['data'].length+1, data['data'][0].values.length+1); 36 | var headerrow = 1; 37 | console.log("STARTED WORKBOOK..."); 38 | 39 | // write header using serie name 40 | for( var j=0; j < data['data'].length; j++ ) { 41 | sheet1.set(j+2, headerrow, data['data'][j].name); 42 | } 43 | 44 | // write category column in the first column 45 | for( var j=0; j < data['data'][0].labels.length; j++ ) { 46 | sheet1.set(1, j+2, data['data'][0].labels[j]); 47 | } 48 | 49 | // for each serie, write out values in its row 50 | for (var i = 0; i < data['data'].length; i++) { 51 | for( var j=0; j < data['data'][i].values.length; j++ ) 52 | { 53 | // col i+2 54 | // row j+1 55 | sheet1.set(i+2, j+2, data['data'][i].values[j]); 56 | } 57 | } 58 | console.log("FILLING WORKBOOK..."); 59 | // Fill some data 60 | // Save it 61 | 62 | 63 | console.log("SAVING WORKBOOK..."); 64 | workbook.save(function(err){ 65 | if (err) { 66 | workbook.cancel(); 67 | callback(err); 68 | } 69 | else { 70 | console.log("SAVED: " + workbook.length); 71 | callback(null, workbook); 72 | } 73 | }); 74 | }; 75 | 76 | 77 | var barChart = { 78 | title: 'Sample bar chart', 79 | renderType: 'bar', 80 | xmlOptions: { 81 | "c:title": { 82 | "c:tx": { 83 | "c:rich": { 84 | "a:p": { 85 | "a:r": { 86 | "a:t": "Override title via XML" 87 | } 88 | } 89 | } 90 | } 91 | } 92 | }, 93 | data: [ 94 | { 95 | name: 'europe', 96 | labels: ['Y2003', 'Y2004', 'Y2005'], 97 | values: [2.5, 2.6, 2.8], 98 | color: 'ff0000' 99 | }, 100 | { 101 | name: 'namerica', 102 | labels: ['Y2003', 'Y2004', 'Y2005'], 103 | values: [2.5, 2.7, 2.9], 104 | color: '00ff00' 105 | }, 106 | { 107 | name: 'asia', 108 | labels: ['Y2003', 'Y2004', 'Y2005'], 109 | values: [2.1, 2.2, 2.4], 110 | color: '0000ff' 111 | }, 112 | { 113 | name: 'lamerica', 114 | labels: ['Y2003', 'Y2004', 'Y2005'], 115 | values: [0.3, 0.3, 0.3], 116 | color: 'ffff00' 117 | }, 118 | { 119 | name: 'meast', 120 | labels: ['Y2003', 'Y2004', 'Y2005'], 121 | values: [0.2, 0.3, 0.3], 122 | color: 'ff00ff' 123 | }, 124 | { 125 | name: 'africa', 126 | labels: ['Y2003', 'Y2004', 'Y2005'], 127 | values: [0.1, 0.1, 0.1], 128 | color: '00ffff' 129 | } 130 | 131 | ] 132 | }; 133 | 134 | createWorkbook(barChart, function(err, workbook) {console.log("ALL DONE"); }); 135 | 136 | //old(barChart, function(err, workbook) {console.log("ALL DONE"); }); -------------------------------------------------------------------------------- /baby/step1_open_save.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // open an existing doc, unzip it, zip it, and save it as new name. See if it opens. 4 | 5 | 6 | var JSZip = require('jszip'); 7 | var fs = require("fs"); 8 | 9 | var INFILE = './lab/out/out.pptx'; 10 | var OUTFILE = './lab/baby.pptx'; 11 | 12 | 13 | // read a zip file 14 | fs.readFile(INFILE, function(err, data) { 15 | if (err) throw err; 16 | var zip = new JSZip(data); 17 | var buffer = zip.generate({type:"nodebuffer"}); 18 | 19 | fs.writeFile(OUTFILE, buffer, function(err) { 20 | if (err) throw err; 21 | }); 22 | }); -------------------------------------------------------------------------------- /baby/step2_transplant_style.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // open an existing doc, unzip it, zip it, and save it as new name. See if it opens. 4 | 5 | 6 | var JSZip = require('jszip'); 7 | var fs = require("fs"); 8 | 9 | var INFILE = './lab/out/out.pptx'; 10 | var OUTFILE = './lab/baby.pptx'; 11 | 12 | var STYLEFILE = './lab/ex1/ex1.pptx'; 13 | 14 | // read a zip file 15 | fs.readFile(INFILE, function(err, data) { 16 | if (err) throw err; 17 | var zip = new JSZip(data); 18 | 19 | 20 | fs.readFile(STYLEFILE, function(err, data2) { 21 | var zip_theme = new JSZip(data2) 22 | var theme_xml = zip_theme.file('ppt/theme/theme1.xml').asText(); 23 | zip.file('ppt/theme/theme1.xml', theme_xml); 24 | 25 | var buffer = zip.generate({type:"nodebuffer"}); 26 | 27 | fs.writeFile(OUTFILE, buffer, function(err) { 28 | if (err) throw err; 29 | }); 30 | }); 31 | 32 | }); -------------------------------------------------------------------------------- /baby/step3_read_minimal.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | var JSZip = require('jszip'); 5 | var fs = require("fs"); 6 | 7 | var INFILE = './lab/minimal/minimal.pptx'; 8 | 9 | // read a zip file 10 | fs.readFile(INFILE, function(err, data) { 11 | if (err) throw err; 12 | var zip = new JSZip(data); 13 | 14 | // ok, yeah, no wow, we've got a pptx file open. Can I modify it somehow 15 | 16 | console.log(Object.keys(zip.files)) 17 | 18 | console.log(zip.file('ppt/slides/slide1.xml').asText()) 19 | 20 | }); 21 | 22 | 23 | 24 | var x = ["[Content_Types].xml", 25 | "_rels/.rels", 26 | "ppt/slides/_rels/slide1.xml.rels", 27 | "ppt/_rels/presentation.xml.rels", 28 | "ppt/presentation.xml", 29 | "ppt/slides/slide1.xml", 30 | "ppt/slideLayouts/_rels/slideLayout6.xml.rels", 31 | "ppt/slideMasters/_rels/slideMaster1.xml.rels", 32 | "ppt/slideLayouts/_rels/slideLayout8.xml.rels", 33 | "ppt/slideLayouts/_rels/slideLayout9.xml.rels", 34 | "ppt/slideLayouts/_rels/slideLayout11.xml.rels", 35 | "ppt/slideLayouts/_rels/slideLayout10.xml.rels", 36 | "ppt/slideLayouts/_rels/slideLayout7.xml.rels", 37 | "ppt/slideLayouts/_rels/slideLayout2.xml.rels", 38 | "ppt/slideLayouts/_rels/slideLayout3.xml.rels", 39 | "ppt/slideLayouts/_rels/slideLayout4.xml.rels", 40 | "ppt/slideLayouts/_rels/slideLayout5.xml.rels", 41 | "ppt/slideLayouts/_rels/slideLayout1.xml.rels", 42 | "ppt/slideLayouts/slideLayout11.xml", 43 | "ppt/slideMasters/slideMaster1.xml", 44 | "ppt/slideLayouts/slideLayout1.xml", 45 | "ppt/slideLayouts/slideLayout2.xml", 46 | "ppt/slideLayouts/slideLayout3.xml", 47 | "ppt/slideLayouts/slideLayout10.xml", 48 | "ppt/slideLayouts/slideLayout4.xml", 49 | "ppt/slideLayouts/slideLayout6.xml", 50 | "ppt/slideLayouts/slideLayout5.xml", 51 | "ppt/slideLayouts/slideLayout9.xml", 52 | "ppt/slideLayouts/slideLayout7.xml", 53 | "ppt/slideLayouts/slideLayout8.xml", 54 | " docProps/thumbnail.jpeg", 55 | "ppt/theme/theme1.xml", 56 | "ppt/viewProps.xml", 57 | "ppt/tableStyles.xml", 58 | "ppt/presProps.xml", 59 | "docProps/app.xml", 60 | "docProps/core.xml", 61 | "ppt/printerSettings/printerSettings1.bin" 62 | ]; 63 | -------------------------------------------------------------------------------- /baby/step4_edit_slide.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | var JSZip = require('jszip'); 5 | var fs = require("fs"); 6 | var cheerio = require('cheerio'); 7 | var pd = require('pretty-data').pd; 8 | 9 | var INFILE = './lab/parts/parts.pptx'; 10 | var OUTFILE = './lab/parts/parts2.pptx'; 11 | 12 | 13 | var $ = cheerio.load 14 | 15 | // read a zip file 16 | fs.readFile(INFILE, function(err, data) { 17 | if (err) throw err; 18 | var zip = new JSZip(data); 19 | var str0 = zip.file('ppt/slides/slide1.xml').asText(); 20 | var $slide1 = cheerio.load(zip.file('ppt/slides/slide1.xml').asText(), {xmlMode: true}); 21 | 22 | var str1 = $slide1.xml(); 23 | console.log([str0.length, str1.length, str0==str1]) 24 | var xml = $slide1.xml(); 25 | 26 | // console.log(pretty); 27 | $slide1('p\\:sp').each(function(i, elem) { 28 | 29 | console.log('---------------------------------------') 30 | $slide1('a\\:t', elem).text("This is different") 31 | var a = $slide1('a\\:t', elem).text(); 32 | console.log(a); 33 | // console.log(pd.xml($(elem, {xmlMode:true}).xml())); 34 | }); 35 | 36 | var str2 = $slide1.xml(); 37 | zip.file('ppt/slides/slide1.xml', str2); 38 | 39 | var buffer = zip.generate({type:"nodebuffer"}); 40 | 41 | fs.writeFile(OUTFILE, buffer, function(err) { 42 | if (err) throw err; 43 | }); 44 | 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /baby/step5_add_shape.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | var JSZip = require('jszip'); 5 | var fs = require("fs"); 6 | var cheerio = require('cheerio'); 7 | var xmldoc = require('xmldoc'); 8 | var pd = require('pretty-data').pd; 9 | var pretty = function(xml) { return pd.xml(xml); } 10 | 11 | var INFILE = './lab/parts/parts.pptx'; 12 | var OUTFILE = './lab/parts/parts2.pptx'; 13 | 14 | function $(xml) { return cheerio.load(xml, {xmlMode: true})} 15 | 16 | function Slide(xml) { 17 | this.$el = cheerio.load(xml, {xmlMode: true}); 18 | return this; 19 | }; 20 | Slide.prototype.xml = function() { return this.$el.xml(); } 21 | 22 | fs.readFile(INFILE, function(err, data) { 23 | if (err) throw err; 24 | var zip = new JSZip(data); 25 | 26 | var slide1xml = zip.file('ppt/slides/slide1.xml').asText(); 27 | var $slide1 = cheerio.load(slide1xml, {xmlMode: true}) 28 | 29 | var $shapes = $slide1('p\\:sp'); 30 | 31 | // textBoxes.each(function(i, elem) { 32 | // console.log(i + "-----------------------------") 33 | // console.log(cheerio.load(elem).xml()) 34 | // }) 35 | // 36 | var $oval =$shapes[3]; 37 | // console.log(($($oval))); 38 | 39 | var str = 'Another circle'; 40 | 41 | $slide1('p\\:spTree').append(str); 42 | 43 | 44 | var str2 = $slide1.xml(); 45 | zip.file('ppt/slides/slide1.xml', str2); 46 | 47 | var buffer = zip.generate({type:"nodebuffer"}); 48 | 49 | fs.writeFile(OUTFILE, buffer, function(err) { 50 | if (err) throw err; 51 | }); 52 | 53 | 54 | 55 | 56 | // 57 | // var $ovalText = $($oval)('a\\:t'); 58 | // 59 | // 60 | // console.log($ovalText.text()); 61 | // console.log($($ovalText[0]).xml() ); 62 | 63 | // console.log(($($oval).xml())); 64 | 65 | 66 | }); 67 | -------------------------------------------------------------------------------- /baby/step6_add_slide.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | var JSZip = require('jszip'); 5 | var fs = require("fs"); 6 | var cheerio = require('cheerio'); 7 | var xmldoc = require('xmldoc'); 8 | var pd = require('pretty-data').pd; 9 | var pretty = function (xml) { 10 | return pd.xml(xml); 11 | } 12 | 13 | var INFILE = './lab/parts/parts.pptx'; 14 | var OUTFILE = './lab/parts2/parts2.pptx'; 15 | var REFERENCE = './lab/parts3/parts3.pptx'; 16 | 17 | 18 | function $(xml) { 19 | return cheerio.load(xml, {xmlMode: true}) 20 | } 21 | 22 | function Slide(xml) { 23 | this.$el = cheerio.load(xml, {xmlMode: true}); 24 | return this; 25 | }; 26 | Slide.prototype.xml = function () { 27 | return this.$el.xml(); 28 | } 29 | 30 | var zip3 = new JSZip(fs.readFileSync(REFERENCE)) 31 | 32 | fs.readFile(INFILE, function (err, data) { 33 | if (err) throw err; 34 | var zip = new JSZip(data); 35 | 36 | 37 | 38 | var slide1xml = zip.file('ppt/slides/slide1.xml').asText(); 39 | var $slide1 = cheerio.load(slide1xml, {xmlMode: true}) 40 | 41 | 42 | // 1. add the slide to ppt/slides/slideN.xml 43 | zip.file('ppt/slides/slide2.xml', slide1xml); 44 | 45 | 46 | // 2. add entry to ppt/_rels/presentation.xml.rels 47 | 48 | var $ppt_rels = $(zip.file('ppt/_rels/presentation.xml.rels').asText()); 49 | var rId3 = ''; 50 | $ppt_rels('Relationships').append(rId3) 51 | zip.file('ppt/_rels/presentation.xml.rels', $ppt_rels.xml()); 52 | 53 | 54 | // 3. add reference to ppt/slides/_rels/ 55 | var xml = ''; 56 | xml += ''; 57 | zip.file('ppt/slides/_rels/slide2.xml.rels', xml) 58 | 59 | // 4. add slide to ppt/presentation.xml 60 | var pres = zip.file('ppt/presentation.xml').asText(); 61 | var $pres = $(pres); 62 | $pres('p\\:sldIdLst').append('') 63 | zip.file('ppt/presentation.xml', $pres.xml()) 64 | 65 | 66 | // HACK! set docProps 67 | 68 | zip.file('docProps/app.xml', '\n148Microsoft Macintosh PowerPointOn-screen Show (4:3)82000falseTheme1Slide Titles2Office ThemeThis is the titleThis is the titleProven, Inc.falsefalsefalse14.0000') 69 | zip.file('ppt/viewProps.xml', '\n') 70 | 71 | 72 | 73 | var $ct = $(zip.file('[Content_Types].xml').asText()); 74 | $ct('Types').append(''); 75 | // console.log(pretty($ct.xml())); 76 | zip.file('[Content_Types].xml', $ct.xml()) 77 | var $slidIdList = 78 | console.log($($pres('p\\:sldIdLst').children()[0]).xml()) 79 | 80 | 81 | 82 | // SYSTEMATICALLY FIND PROBLEM 83 | var diffs = [ 84 | // '[Content_Types].xml', 85 | // 'ppt/_rels/presentation.xml.rels', 86 | // 'docProps/core.xml', 87 | // 'ppt/slides/slide2.xml' 88 | ]; 89 | // var diffs = Object.keys(zip3.files); 90 | // console.log(diffs); 91 | diffs.forEach(function(key) { 92 | zip.file(key, zip3.file(key).asArrayBuffer()) 93 | }) 94 | // 95 | 96 | var buffer = zip.generate({type: "nodebuffer"}); 97 | 98 | fs.writeFile(OUTFILE, buffer, function (err) { 99 | if (err) throw err; 100 | }); 101 | // 102 | 103 | }); 104 | -------------------------------------------------------------------------------- /baby/step7_diff.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | var JSZip = require('jszip'); 5 | var fs = require("fs"); 6 | 7 | //var FILE1 = './test/files/parts3-b.pptx'; 8 | //var FILE2 = './test/files/parts3-a.pptx'; 9 | 10 | var FILE1 = './lab/p0/protobi-2015-09-03 17.06.pptx'; 11 | var FILE2 = './lab/p1/protobi-2015-09-03 17.06.pptx'; 12 | 13 | var zip1 = new JSZip(fs.readFileSync(FILE1)); 14 | var zip2 = new JSZip(fs.readFileSync(FILE2)); 15 | Object.keys(zip1.files).forEach(function (key) { 16 | var str1 = zip1.file(key).asText().replace(/\n|\s/ig, ''); 17 | var str2 = zip2.file(key) ? zip2.file(key).asText().replace(/\n|\s/ig, '') : ""; 18 | 19 | // if (str1 != str2) { 20 | if (str1.length != str2.length) { 21 | 22 | // if (str1.length != str2.length) { 23 | console.log(key); 24 | console.log(str1.length, str2.length, str1.length == str2.length) 25 | } 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /baby/step8_frankenstein_copy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | var JSZip = require('jszip'); 5 | var fs = require("fs"); 6 | var cheerio = require('cheerio'); 7 | var xmldoc = require('xmldoc'); 8 | var pd = require('pretty-data').pd; 9 | var pretty = function(xml) { return pd.xml(xml); } 10 | 11 | var INFILE = './lab/parts3/parts3.pptx'; 12 | var OUTFILE = './lab/parts2/parts2.pptx'; 13 | 14 | function $(xml) { return cheerio.load(xml, {xmlMode: true})} 15 | 16 | fs.readFile(INFILE, function(err, data) { 17 | if (err) throw err; 18 | var zip1 = new JSZip(data); 19 | 20 | var zip2 = new JSZip(); 21 | 22 | 23 | Object.keys(zip1.files).forEach(function(key) { 24 | console.log(key) 25 | zip2.file(key, zip1.file(key).asText() ) ; 26 | }); 27 | 28 | 29 | var buffer = zip2.generate({type:"nodebuffer"}); 30 | 31 | fs.writeFile(OUTFILE, buffer, function(err) { 32 | if (err) throw err; 33 | }); 34 | // 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /baby/step9_xml_parse_generate.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require("fs"); 4 | var PPTX = require('../lib/pptx'); 5 | 6 | var INFILE = './lab/parts3/parts3.pptx'; 7 | var OUTFILE = './lab/parts2/parts2.pptx'; 8 | 9 | 10 | fs.readFile(INFILE, function(err, data) { 11 | if (err) throw err; 12 | var pptx = new PPTX(); 13 | pptx.load(data, function (err) { 14 | fs.writeFile('./lab/parts2/parts2.json', JSON.stringify(pptx, null,4), 'utf8'); 15 | fs.writeFile(OUTFILE, pptx.toBuffer(), function (err) { 16 | if (err) throw err; 17 | }); 18 | }); 19 | }); 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /baby/t.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | var JSZip = require('jszip'); 5 | var fs = require("fs"); 6 | 7 | var TESTFILE = '/tmp/chart.pptx'; 8 | var REFERENCE = './lab/chart/chart.pptx'; 9 | var OUTFILE = '/tmp/chart2.pptx'; 10 | 11 | function $(xml) { 12 | return cheerio.load(xml, {xmlMode: true}) 13 | } 14 | 15 | 16 | var testZip = new JSZip(fs.readFileSync(TESTFILE)); 17 | var refZip = new JSZip(fs.readFileSync(REFERENCE)); 18 | var outZip = new JSZip(); 19 | 20 | //console.log(Object.keys(refZip.files)); 21 | //process.exit() 22 | 23 | // copy all the files from testzip to outzip 24 | // copy selected files from refzip to outzuip 25 | // save outzip 26 | 27 | Object.keys(testZip.files).forEach(function (key) { 28 | outZip.file(key, testZip.file(key).asArrayBuffer()); 29 | }); 30 | 31 | var keys = [ 32 | // '[Content_Types].xml', 33 | // '_rels/.rels', 34 | // 'ppt/slides/_rels/slide2.xml.rels', 35 | // 'ppt/slides/_rels/slide1.xml.rels', 36 | // 'ppt/_rels/presentation.xml.rels', 37 | // 'ppt/presentation.xml', 38 | // 'ppt/slides/slide2.xml', 39 | // 'ppt/slides/slide1.xml', 40 | // 'ppt/slideLayouts/_rels/slideLayout8.xml.rels', 41 | // 'ppt/slideLayouts/_rels/slideLayout9.xml.rels', 42 | // 'ppt/slideLayouts/_rels/slideLayout5.xml.rels', 43 | // 'ppt/slideLayouts/_rels/slideLayout6.xml.rels', 44 | // 'ppt/slideLayouts/_rels/slideLayout4.xml.rels', 45 | // 'ppt/slideLayouts/_rels/slideLayout2.xml.rels', 46 | // 'ppt/slideLayouts/_rels/slideLayout1.xml.rels', 47 | // 'ppt/slideLayouts/_rels/slideLayout11.xml.rels', 48 | // 'ppt/slideLayouts/_rels/slideLayout10.xml.rels', 49 | // 'ppt/slideMasters/_rels/slideMaster1.xml.rels', 50 | // 'ppt/slideLayouts/_rels/slideLayout3.xml.rels', 51 | // 'ppt/slideLayouts/slideLayout10.xml', 52 | // 'ppt/slideLayouts/slideLayout9.xml', 53 | // 'ppt/slideLayouts/slideLayout2.xml', 54 | // 'ppt/slideLayouts/slideLayout1.xml', 55 | // 'ppt/slideMasters/slideMaster1.xml', 56 | // 'ppt/slideLayouts/_rels/slideLayout7.xml.rels', 57 | // 'ppt/slideLayouts/slideLayout3.xml', 58 | // 'ppt/slideLayouts/slideLayout4.xml', 59 | // 'ppt/slideLayouts/slideLayout5.xml', 60 | // 'ppt/slideLayouts/slideLayout6.xml', 61 | // 'ppt/slideLayouts/slideLayout7.xml', 62 | // 'ppt/slideLayouts/slideLayout8.xml', 63 | // 'ppt/slideLayouts/slideLayout11.xml', 64 | // 'ppt/embeddings/Microsoft_Excel_Sheet2.xlsx', 65 | // 'ppt/charts/_rels/chart2.xml.rels', 66 | // 'ppt/theme/theme1.xml', 67 | // 'ppt/charts/chart1.xml', 68 | // 'ppt/embeddings/Microsoft_Excel_Sheet1.xlsx', 69 | // 'ppt/charts/chart2.xml', 70 | // 'docProps/thumbnail.jpeg', 71 | // 'ppt/charts/_rels/chart1.xml.rels', 72 | // 'ppt/viewProps.xml', 73 | // 'ppt/tableStyles.xml', 74 | // 'ppt/presProps.xml', 75 | // 'docProps/app.xml', 76 | // 'docProps/core.xml', 77 | // 'ppt/printerSettings/printerSettings1.bin' 78 | ] 79 | 80 | keys.forEach(function (key) { 81 | outZip.file(key, refZip.file(key).asArrayBuffer()); 82 | }); 83 | 84 | var buffer = outZip.generate({type: "nodebuffer"}); 85 | 86 | fs.writeFile(OUTFILE, buffer, function (err) { 87 | if (err) throw err; 88 | console.log("open "+OUTFILE) 89 | }); 90 | -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require("fs"); 4 | var PPTX = require('..'); 5 | 6 | 7 | var INFILE = './test/files/minimal.pptx'; // a blank PPTX file with my layouts, themes, masters. 8 | var OUTFILE = '/tmp/example.pptx'; 9 | 10 | fs.readFile(INFILE, function (err, data) { 11 | if (err) throw err; 12 | var pptx = new PPTX.Presentation(); 13 | pptx.load(data, function (err) { 14 | var slide1 = pptx.getSlide('slide1'); 15 | 16 | var slide2 = pptx.addSlide("slideLayout3"); // section divider 17 | var slide3 = pptx.addSlide("slideLayout6"); // title only 18 | 19 | 20 | var triangle = slide1.addShape() 21 | .text("Triangle") 22 | .shapeProperties() 23 | .x(PPTX.emu.inch(2)) 24 | .y(PPTX.emu.inch(2)) 25 | .cx(PPTX.emu.inch(2)) 26 | .cy(PPTX.emu.inch(2)) 27 | .prstGeom('triangle'); 28 | 29 | var triangle = slide1.addShape() 30 | .text("Ellipse") 31 | .shapeProperties() 32 | .x(PPTX.emu.inch(4)) 33 | .y(PPTX.emu.inch(4)) 34 | .cx(PPTX.emu.inch(2)) 35 | .cy(PPTX.emu.inch(1)) 36 | .prstGeom('ellipse'); 37 | 38 | for (var i = 0; i < 20; i++) { 39 | slide2.addShape() 40 | .text("" + i) 41 | .shapeProperties() 42 | .x(PPTX.emu.inch((Math.random() * 10))) 43 | .y(PPTX.emu.inch((Math.random() * 6))) 44 | .cx(PPTX.emu.inch(1)) 45 | .cy(PPTX.emu.inch(1)) 46 | .prstGeom('ellipse'); 47 | } 48 | 49 | slide1.getShapes()[3] 50 | .text("Now it's a trapezoid") 51 | .shapeProperties() 52 | .x(PPTX.emu.inch(1)) 53 | .y(PPTX.emu.inch(1)) 54 | .cx(PPTX.emu.inch(2)) 55 | .cy(PPTX.emu.inch(0.75)) 56 | .prstGeom('trapezoid'); 57 | 58 | var chart = slide3.addChart(barChart, function (err, chart) { 59 | 60 | fs.writeFile(OUTFILE, pptx.toBuffer(), function (err) { 61 | if (err) throw err; 62 | console.log("open " + OUTFILE) 63 | }); 64 | }); 65 | }); 66 | }) 67 | ; 68 | 69 | var barChart = { 70 | title: 'Sample bar chart', 71 | renderType: 'bar', 72 | data: [ 73 | { 74 | name: 'Series 1', 75 | labels: ['Category 1', 'Category 2', 'Category 3', 'Category 4'], 76 | values: [4.3, 2.5, 3.5, 4.5] 77 | }, 78 | { 79 | name: 'Series 2', 80 | labels: ['Category 1', 'Category 2', 'Category 3', 'Category 4'], 81 | values: [2.4, 4.4, 1.8, 2.8] 82 | }, 83 | { 84 | name: 'Series 3', 85 | labels: ['Category 1', 'Category 2', 'Category 3', 'Category 4'], 86 | values: [2.0, 2.0, 3.0, 5.0] 87 | } 88 | ] 89 | } -------------------------------------------------------------------------------- /lib/chart.js: -------------------------------------------------------------------------------- 1 | var excelbuilder = require('./msexcel-builder'); 2 | var fs = require('fs'); 3 | var clone = require('./util/clone') 4 | 5 | 6 | var Chart = module.exports = function (args) { 7 | this.presentation = args.presentation; // should this be inferred from the slide? 8 | this.slide = args.slide; 9 | this.name = args.name; 10 | } 11 | 12 | var XmlNode = require('./xmlnode'); 13 | 14 | // TODO Generate Excel Worksheet for dataseries 15 | // TODO Generate data series reference for chart xml 16 | 17 | 18 | Chart.prototype.load = function (chartInfo, done) { 19 | var self = this; 20 | var chartName = this.name; // e.g. 'chart1'; 21 | 22 | var jsChartFrame = clone(require('./fragments/js/chartframe')); 23 | jsChartFrame["p:graphicFrame"]["p:nvGraphicFramePr"]["p:nvPr"]["p:extLst"]["p:ext"]["p14:modId"] = 24 | { 25 | "$": { 26 | "xmlns:p14": "http://schemas.microsoft.com/office/powerpoint/2010/main", 27 | "val": Math.floor(Math.random() * 4294967295) 28 | } 29 | } 30 | self.content = jsChartFrame["p:graphicFrame"]; 31 | 32 | 33 | // 'ppt/charts/chartN.xml' 34 | var chartContent = this.getChartBase(chartInfo); 35 | this.presentation.registerChart(chartName, chartContent); 36 | 37 | 38 | this.createWorkbook(chartInfo, function (err, workbookJSZip) { 39 | self.presentation.registerChartWorkbook(chartName, workbookJSZip.generate({type: 'arraybuffer'})); 40 | done(null, self); 41 | }); 42 | } 43 | 44 | Chart.prototype.getChartBase = function (chartInfo) { 45 | var jsChart = clone(require('./fragments/js/chart')); 46 | var jsChartSeries = this.getChartSeries(chartInfo); 47 | 48 | // TODO Generate the chart series from the data 49 | jsChart["c:chartSpace"]["c:chart"][0]["c:plotArea"][0]["c:barChart"][0]["c:ser"] = jsChartSeries['c:ser']; 50 | return jsChart; 51 | }; 52 | 53 | Chart.prototype.getChartSeries = function (chartInfo) { 54 | var res = { 55 | "c:ser": chartInfo.data.map(this._ser, this) 56 | } 57 | return res; 58 | }; 59 | 60 | 61 | /// @brief returns XML snippet for a chart dataseries 62 | Chart.prototype._ser = function (serie, i) { 63 | var rc2a = this._rowColToSheetAddress; // shortcut 64 | 65 | 66 | var ser = XmlNode() 67 | .addChild('c:idx', XmlNode().attr('val', i)) 68 | .addChild('c:order', XmlNode().attr('val', i)) 69 | .addChild('c:tx', this._strRef('Sheet1!' + rc2a(1, 2 + i, true, true), [serie.name])) 70 | .addChild('c:invertIfNegative', XmlNode().attr('val', 0)) 71 | .addChild('c:cat', this._strRef('Sheet1!' + rc2a(2, 1, true, true) + ':' + rc2a(2 + serie.labels.length - 1, 1, true, true), serie.labels)) 72 | .addChild('c:val', this._numRef('Sheet1!' + rc2a(2, 2 + i, true, true) + ':' + rc2a(2 + serie.labels.length - 1, 2 + i, true, true), serie.values, "General")) 73 | 74 | if (serie.color) { 75 | ser.addChild('c:spPr', 76 | XmlNode().addChild('a:solidFill', 77 | XmlNode().addChild('a:srgbClr', 78 | XmlNode().attr('val', serie.color) 79 | ) 80 | ) 81 | ); 82 | } 83 | else if (serie.schemeColor) { 84 | ser.addChild('c:spPr', 85 | XmlNode().addChild('a:solidFill', 86 | XmlNode().addChild('a:schemeClr', 87 | XmlNode().attr('val', serie.schemeColor) 88 | ) 89 | ) 90 | ); 91 | } 92 | 93 | return ser.el; 94 | }; 95 | 96 | 97 | /// 98 | /// @brief Transform an array of string into an office's compliance structure 99 | /// 100 | /// @param[in] region String 101 | /// The reference cell of the string, for example: $A$1 102 | /// @param[in] stringArr 103 | /// An array of string, for example: ['foo', 'bar'] 104 | /// 105 | Chart.prototype._strRef = function (region, stringArr) { 106 | 107 | var strRef = XmlNode().addChild('c:strRef', XmlNode() 108 | .addChild('c:f', region) 109 | .addChild('c:strCache', this._strCache(stringArr)) 110 | ); 111 | return strRef.el; 112 | } 113 | 114 | 115 | Chart.prototype._strCache = function (stringArr) { 116 | var strRef = XmlNode().addChild('c:ptCount', XmlNode().attr('val', stringArr.length)) 117 | for (var i = 0; i < stringArr.length; i++) { 118 | strRef.addChild('c:pt', XmlNode().attr('idx', i).addChild('c:v', stringArr[i])) 119 | } 120 | 121 | return strRef; 122 | } 123 | 124 | 125 | /// 126 | /// @brief Transform an array of numbers into an office's compliance structure 127 | /// 128 | /// @param[in] region String 129 | /// The reference cell of the string, for example: $A$1 130 | /// @param[in] numArr 131 | /// An array of numArr, for example: [4, 7, 8] 132 | /// @param[in] formatCode 133 | /// A string describe the number's format. Example: General 134 | /// 135 | Chart.prototype._numRef = function (region, numArr, formatCode) { 136 | 137 | var numCache = XmlNode() 138 | .addChild('c:formatCode', formatCode) 139 | .addChild('c:ptCount', XmlNode().attr('val', numArr.length)); 140 | 141 | for (var i = 0; i < numArr.length; i++) { 142 | numCache.addChild('c:pt', XmlNode().attr('idx', i).addChild('c:v', numArr[i].toString())); 143 | } 144 | 145 | var numRef = XmlNode().addChild('c:numRef', XmlNode() 146 | .addChild('c:f', region) 147 | .addChild('c:numCache', numCache) 148 | ); 149 | return numRef.el; 150 | 151 | } 152 | 153 | 154 | Chart.prototype._rowColToSheetAddress = function (row, col, isRowAbsolute, isColAbsolute) { 155 | var address = ""; 156 | 157 | if (isColAbsolute) 158 | address += '$'; 159 | 160 | // these lines of code will transform the number 1-26 into A->Z 161 | // used in excel's cell's coordination 162 | while (col > 0) { 163 | var num = col % 26; 164 | col = (col - num ) / 26; 165 | address += String.fromCharCode(65 + num - 1); 166 | } 167 | 168 | if (isRowAbsolute) 169 | address += '$'; 170 | 171 | address += row; 172 | 173 | return address; 174 | }; 175 | 176 | 177 | // takes an array with series data 178 | // callback takes two parameters: 179 | // @err Error, null if successful 180 | // @wb JSZip object containing the workbook 181 | Chart.prototype.createWorkbook = function (data, callback) { 182 | 183 | // First, generate a temporatory excel file for storing the chart's data 184 | var workbook = excelbuilder.createWorkbook(); 185 | 186 | // Create a new worksheet with 10 columns and 12 rows 187 | // number of columns: data['data'].length+1 -> equaly number of series 188 | // number of rows: data['data'][0].values.length+1 189 | var sheet1 = workbook.createSheet('Sheet1', data['data'].length + 1, data['data'][0].values.length + 1); 190 | var headerrow = 1; 191 | 192 | // write header using serie name 193 | for (var j = 0; j < data['data'].length; j++) { 194 | sheet1.set(j + 2, headerrow, data['data'][j].name); 195 | } 196 | 197 | // write category column in the first column 198 | for (var j = 0; j < data['data'][0].labels.length; j++) { 199 | sheet1.set(1, j + 2, data['data'][0].labels[j]); 200 | } 201 | 202 | // for each serie, write out values in its row 203 | for (var i = 0; i < data['data'].length; i++) { 204 | for (var j = 0; j < data['data'][i].values.length; j++) { 205 | // col i+2 206 | // row j+1 207 | sheet1.set(i + 2, j + 2, data['data'][i].values[j]); 208 | } 209 | } 210 | workbook.generate(callback); // returns (err, zip) 211 | }; -------------------------------------------------------------------------------- /lib/charts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | null: function(options) { 3 | return { 4 | "c:chartSpace": { 5 | "@xmlns:c": "http://schemas.openxmlformats.org/drawingml/2006/chart", 6 | "@xmlns:a": "http://schemas.openxmlformats.org/drawingml/2006/main", 7 | "@xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", 8 | "c:lang": { "@val": "en-US" }, 9 | "c:date1904": { "@val": "1" }, 10 | "c:chart": {} 11 | } 12 | } 13 | }, 14 | 15 | "bar": function (options) { 16 | options = options || {}; 17 | return { 18 | "c:chartSpace": { 19 | "@xmlns:c": "http://schemas.openxmlformats.org/drawingml/2006/chart", 20 | "@xmlns:a": "http://schemas.openxmlformats.org/drawingml/2006/main", 21 | "@xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", 22 | "c:lang": { "@val": "en-US" }, 23 | "c:chart": { 24 | "c:plotArea": { 25 | "c:layout": {}, 26 | "c:barChart": { 27 | "c:barDir": { "@val": "bar" }, 28 | "c:grouping": { "@val": "clustered" }, 29 | "#list": [ 30 | {"c:axId": { "@val": "64451712" }}, 31 | {"c:axId": { "@val": "64453248" }} 32 | ] 33 | }, 34 | "c:catAx": { 35 | "c:axId": { "@val": "64451712" }, 36 | "c:scaling": { 37 | "c:orientation": { "@val": options.catAxisReverseOrder ? "maxMin" : "minMax" } 38 | }, 39 | "c:axPos": { "@val": "l" }, 40 | "c:tickLblPos": { "@val": "nextTo" }, 41 | "c:crossAx": { "@val": "64453248" }, 42 | "c:crosses": { "@val": "autoZero" }, 43 | "c:auto": { "@val": "1" }, 44 | "c:lblAlgn": { "@val": "ctr" }, 45 | "c:lblOffset": { "@val": "100" } 46 | }, 47 | "c:valAx": { 48 | "c:axId": { "@val": "64453248" }, 49 | "c:scaling": { 50 | "c:orientation": { "@val": "minMax" } 51 | }, 52 | "c:axPos": { "@val": "b" }, 53 | // "c:majorGridlines": {}, 54 | "c:numFmt": { 55 | "@formatCode": "General", 56 | "@sourceLinked": "1" 57 | }, 58 | "c:tickLblPos": { "@val": "nextTo" }, 59 | "c:crossAx": { "@val": "64451712" }, 60 | "c:crosses": { "@val": options.valAxisCrossAtMaxCategory ? "max" : "autoZero" }, 61 | "c:crossBetween": { "@val": "between" } 62 | } 63 | }, 64 | "c:legend": { 65 | "c:legendPos": { "@val": "r" }, 66 | "c:layout": {} 67 | }, 68 | "c:plotVisOnly": { "@val": "1" } 69 | }, 70 | "c:txPr": { 71 | "a:bodyPr": {}, 72 | "a:lstStyle": {}, 73 | "a:p": { 74 | "a:pPr": { 75 | "a:defRPr": { "@sz": "1800" } 76 | }, 77 | "a:endParaRPr": { "@lang": "en-US" } 78 | } 79 | }, 80 | "c:externalData": { "@r:id": "rId1" } 81 | } 82 | } 83 | }, 84 | "column": function (options) { 85 | options = options || {}; 86 | return { 87 | "c:chartSpace": { 88 | "@xmlns:c": "http://schemas.openxmlformats.org/drawingml/2006/chart", 89 | "@xmlns:a": "http://schemas.openxmlformats.org/drawingml/2006/main", 90 | "@xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", 91 | "c:lang": { "@val": "en-US" }, 92 | "c:date1904": { "@val": "1" }, 93 | "c:chart": { 94 | "c:plotArea": { 95 | "c:layout": {}, 96 | "c:barChart": { 97 | "c:barDir": { "@val": "col" }, 98 | "c:grouping": { "@val": "clustered" }, 99 | "c:overlap": { "@val": options.overlap || "0" }, 100 | "c:gapWidth": { "@val": options.gapWidth || "150" }, 101 | 102 | "#list": [ 103 | {"c:axId": { "@val": "64451712" }}, 104 | {"c:axId": { "@val": "64453248" }} 105 | ] 106 | }, 107 | "c:catAx": { 108 | "c:axId": { "@val": "64451712" }, 109 | "c:scaling": { 110 | "c:orientation": { "@val": options.catAxisReverseOrder ? "maxMin" : "minMax" } 111 | }, 112 | "c:axPos": { "@val": "l" }, 113 | "c:tickLblPos": { "@val": "nextTo" }, 114 | "c:crossAx": { "@val": "64453248" }, 115 | "c:crosses": { "@val": "autoZero" }, 116 | "c:auto": { "@val": "1" }, 117 | "c:lblAlgn": { "@val": "ctr" }, 118 | "c:lblOffset": { "@val": "100" } 119 | }, 120 | "c:valAx": { 121 | "c:axId": { "@val": "64453248" }, 122 | "c:scaling": { 123 | "c:orientation": { "@val": "minMax" } 124 | }, 125 | "c:axPos": { "@val": "b" }, 126 | // "c:majorGridlines": {}, 127 | "c:numFmt": { 128 | "@formatCode": "General", 129 | "@sourceLinked": "1" 130 | }, 131 | "c:tickLblPos": { "@val": "nextTo" }, 132 | "c:crossAx": { "@val": "64451712" }, 133 | "c:crosses": { "@val": options.valAxisCrossAtMaxCategory ? "max" : "autoZero"}, 134 | "c:crossBetween": { "@val": "between" } 135 | } 136 | }, 137 | "c:legend": { 138 | "c:legendPos": { "@val": "r" }, 139 | "c:layout": {} 140 | }, 141 | "c:plotVisOnly": { "@val": "1" } 142 | }, 143 | "c:txPr": { 144 | "a:bodyPr": {}, 145 | "a:lstStyle": {}, 146 | "a:p": { 147 | "a:pPr": { 148 | "a:defRPr": { "@sz": "1800" } 149 | }, 150 | "a:endParaRPr": { "@lang": "en-US" } 151 | } 152 | }, 153 | "c:externalData": { "@r:id": "rId1" } 154 | } 155 | } 156 | }, 157 | "group-bar": function (options) { 158 | options = options || {}; 159 | return { 160 | "c:chartSpace": { 161 | "@xmlns:c": "http://schemas.openxmlformats.org/drawingml/2006/chart", 162 | "@xmlns:a": "http://schemas.openxmlformats.org/drawingml/2006/main", 163 | "@xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", 164 | "c:lang": { "@val": "en-US" }, 165 | "c:date1904": { "@val": "1" }, 166 | "c:chart": { 167 | "c:plotArea": { 168 | "c:layout": {}, 169 | "c:barChart": { 170 | "c:barDir": { "@val": "bar" }, 171 | "c:grouping": { "@val": "stacked" }, 172 | "c:overlap": { "@val": options.overlap || "100" }, 173 | "c:gapWidth": { "@val": options.gapWidth || "150" }, 174 | "#list": [ 175 | {"c:axId": { "@val": "64451712" }}, 176 | {"c:axId": { "@val": "64453248" }} 177 | ] 178 | }, 179 | "c:catAx": { 180 | "c:axId": { "@val": "64451712" }, 181 | "c:scaling": { 182 | "c:orientation": { "@val": options.catAxisReverseOrder ? "maxMin" : "minMax" } 183 | }, 184 | "c:axPos": { "@val": "l" }, 185 | "c:tickLblPos": { "@val": "nextTo" }, 186 | "c:crossAx": { "@val": "64453248" }, 187 | "c:crosses": { "@val": "autoZero" }, 188 | "c:auto": { "@val": "1" }, 189 | "c:lblAlgn": { "@val": "ctr" }, 190 | "c:lblOffset": { "@val": "100" } 191 | }, 192 | "c:valAx": { 193 | "c:axId": { "@val": "64453248" }, 194 | "c:scaling": { 195 | "c:orientation": { "@val": "minMax" } 196 | }, 197 | "c:axPos": { "@val": "b" }, 198 | // "c:majorGridlines": {}, 199 | "c:numFmt": { 200 | "@formatCode": "General", 201 | "@sourceLinked": "1" 202 | }, 203 | "c:tickLblPos": { "@val": "nextTo" }, 204 | "c:crossAx": { "@val": "64451712" }, 205 | "c:crosses": { "@val": options.valAxisCrossAtMaxCategory ? "max" : "autoZero" }, 206 | "c:crossBetween": { "@val": "between" } 207 | } 208 | }, 209 | "c:legend": { 210 | "c:legendPos": { "@val": "r" }, 211 | "c:layout": {} 212 | }, 213 | "c:plotVisOnly": { "@val": "1" } 214 | }, 215 | "c:txPr": { 216 | "a:bodyPr": {}, 217 | "a:lstStyle": {}, 218 | "a:p": { 219 | "a:pPr": { 220 | "a:defRPr": { "@sz": "1800" } 221 | }, 222 | "a:endParaRPr": { "@lang": "en-US" } 223 | } 224 | }, 225 | "c:externalData": { "@r:id": "rId1" } 226 | } 227 | } 228 | }, 229 | "pie": function (options) { 230 | options = options || {}; 231 | return { 232 | "c:chartSpace": { 233 | "@xmlns:c": "http://schemas.openxmlformats.org/drawingml/2006/chart", 234 | "@xmlns:a": "http://schemas.openxmlformats.org/drawingml/2006/main", 235 | "@xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", 236 | "c:lang": { "@val": "en-US" }, 237 | "c:chart": { 238 | "c:title": { 239 | "c:layout": {} 240 | }, 241 | "c:plotArea": { 242 | "c:layout": {}, 243 | "c:pieChart": { 244 | "c:varyColors": { "@val": "1" }, 245 | "c:firstSliceAng": { "@val": "0" }, 246 | "#list": [] 247 | } 248 | }, 249 | 250 | "c:legend": { 251 | "c:legendPos": { "@val": "r" }, 252 | "c:layout": {} 253 | }, 254 | "c:plotVisOnly": { "@val": "1" } 255 | }, 256 | "c:txPr": { 257 | "a:bodyPr": {}, 258 | "a:lstStyle": {}, 259 | "a:p": { 260 | "a:pPr": { 261 | "a:defRPr": { "@sz": "1800" } 262 | }, 263 | "a:endParaRPr": { "@lang": "en-US" } 264 | } 265 | }, 266 | "c:externalData": { "@r:id": "rId1" } 267 | } 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /lib/defaults.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "p:sp": { 3 | "p:nvSpPr": [ 4 | { 5 | "p:cNvPr": [ 6 | { 7 | "$": { 8 | "id": "5", 9 | "name": "Oval 4" 10 | } 11 | } 12 | ], 13 | "p:cNvSpPr": [ 14 | "" 15 | ], 16 | "p:nvPr": [ 17 | "" 18 | ] 19 | } 20 | ], 21 | "p:spPr": [ 22 | { 23 | "a:xfrm": [ 24 | { 25 | "a:off": [ 26 | { 27 | "$": { 28 | "x": "6578600", 29 | "y": "787400" 30 | } 31 | } 32 | ], 33 | "a:ext": [ 34 | { 35 | "$": { 36 | "cx": "1181100", 37 | "cy": "1181100" 38 | } 39 | } 40 | ] 41 | } 42 | ], 43 | "a:prstGeom": [ 44 | { 45 | "$": { 46 | "prst": "ellipse" 47 | }, 48 | "a:avLst": [ 49 | "" 50 | ] 51 | } 52 | ] 53 | } 54 | ], 55 | "p:style": [ 56 | { 57 | "a:lnRef": [ 58 | { 59 | "$": { 60 | "idx": "1" 61 | }, 62 | "a:schemeClr": [ 63 | { 64 | "$": { 65 | "val": "accent1" 66 | } 67 | } 68 | ] 69 | } 70 | ], 71 | "a:fillRef": [ 72 | { 73 | "$": { 74 | "idx": "3" 75 | }, 76 | "a:schemeClr": [ 77 | { 78 | "$": { 79 | "val": "accent1" 80 | } 81 | } 82 | ] 83 | } 84 | ], 85 | "a:effectRef": [ 86 | { 87 | "$": { 88 | "idx": "2" 89 | }, 90 | "a:schemeClr": [ 91 | { 92 | "$": { 93 | "val": "accent1" 94 | } 95 | } 96 | ] 97 | } 98 | ], 99 | "a:fontRef": [ 100 | { 101 | "$": { 102 | "idx": "minor" 103 | }, 104 | "a:schemeClr": [ 105 | { 106 | "$": { 107 | "val": "lt1" 108 | } 109 | } 110 | ] 111 | } 112 | ] 113 | } 114 | ], 115 | "p:txBody": [ 116 | { 117 | "a:bodyPr": [ 118 | { 119 | "$": { 120 | "rtlCol": "0", 121 | "anchor": "ctr" 122 | } 123 | } 124 | ], 125 | "a:lstStyle": [ 126 | "" 127 | ], 128 | "a:p": [ 129 | { 130 | "a:pPr": [ 131 | { 132 | "$": { 133 | "algn": "ctr" 134 | } 135 | } 136 | ], 137 | "a:r": [ 138 | { 139 | "a:rPr": [ 140 | { 141 | "$": { 142 | "lang": "en-US", 143 | "dirty": "0", 144 | "smtClean": "0" 145 | } 146 | } 147 | ], 148 | "a:t": [ 149 | "A default circle" 150 | ] 151 | } 152 | ], 153 | "a:endParaRPr": [ 154 | { 155 | "$": { 156 | "lang": "en-US", 157 | "dirty": "0" 158 | } 159 | } 160 | ] 161 | } 162 | ] 163 | } 164 | ] 165 | } 166 | }; -------------------------------------------------------------------------------- /lib/fragment.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var xml2js = require('xml2js'); 3 | 4 | module.exports = { 5 | fromXml: function(name, callback) { 6 | fs.readFile(__dirname + '/fragments/' + name, 'utf8', function(err, xml) { 7 | if (err) callback(err); 8 | xml2js.parseString(xml,{explicitArray : false}, function (err, js) { 9 | callback(null, js); 10 | }); 11 | }) 12 | }, 13 | 14 | fromBinary: function(name, callback) { 15 | fs.readFile(__dirname + '/fragments/' + name, function(err, data) { 16 | if (err) callback(err); 17 | callback(null, data ); 18 | }) 19 | } 20 | } -------------------------------------------------------------------------------- /lib/fragments/Microsoft_Excel_Sheet1.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/won21kr/js-pptx/7aed5fb3cc88502a58993603936d71eea603bbfc/lib/fragments/Microsoft_Excel_Sheet1.xlsx -------------------------------------------------------------------------------- /lib/fragments/js/chart.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "c:chartSpace": { 3 | "$": { 4 | "xmlns:c": "http://schemas.openxmlformats.org/drawingml/2006/chart", 5 | "xmlns:a": "http://schemas.openxmlformats.org/drawingml/2006/main", 6 | "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships" 7 | }, 8 | "c:date1904": [ 9 | { 10 | "$": { 11 | "val": "0" 12 | } 13 | } 14 | ], 15 | "c:lang": [ 16 | { 17 | "$": { 18 | "val": "en-US" 19 | } 20 | } 21 | ], 22 | "c:roundedCorners": [ 23 | { 24 | "$": { 25 | "val": "0" 26 | } 27 | } 28 | ], 29 | "mc:AlternateContent": [ 30 | { 31 | "$": { 32 | "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006" 33 | }, 34 | "mc:Choice": [ 35 | { 36 | "$": { 37 | "Requires": "c14", 38 | "xmlns:c14": "http://schemas.microsoft.com/office/drawing/2007/8/2/chart" 39 | }, 40 | "c14:style": [ 41 | { 42 | "$": { 43 | "val": "118" 44 | } 45 | } 46 | ] 47 | } 48 | ], 49 | "mc:Fallback": [ 50 | { 51 | "c:style": [ 52 | { 53 | "$": { 54 | "val": "18" 55 | } 56 | } 57 | ] 58 | } 59 | ] 60 | } 61 | ], 62 | "c:chart": [ 63 | { 64 | "c:autoTitleDeleted": [ 65 | { 66 | "$": { 67 | "val": "0" 68 | } 69 | } 70 | ], 71 | "c:plotArea": [ 72 | { 73 | "c:layout": [ 74 | "" 75 | ], 76 | "c:barChart": [ 77 | { 78 | "c:barDir": [ 79 | { 80 | "$": { 81 | "val": "bar" 82 | } 83 | } 84 | ], 85 | "c:grouping": [ 86 | { 87 | "$": { 88 | "val": "clustered" 89 | } 90 | } 91 | ], 92 | "c:varyColors": [ 93 | { 94 | "$": { 95 | "val": "0" 96 | } 97 | } 98 | ], 99 | "c:ser": [], // insert generated c:ser here 100 | "c:dLbls": [ 101 | { 102 | "c:showLegendKey": [ 103 | { 104 | "$": { 105 | "val": "0" 106 | } 107 | } 108 | ], 109 | "c:showVal": [ 110 | { 111 | "$": { 112 | "val": "0" 113 | } 114 | } 115 | ], 116 | "c:showCatName": [ 117 | { 118 | "$": { 119 | "val": "0" 120 | } 121 | } 122 | ], 123 | "c:showSerName": [ 124 | { 125 | "$": { 126 | "val": "0" 127 | } 128 | } 129 | ], 130 | "c:showPercent": [ 131 | { 132 | "$": { 133 | "val": "0" 134 | } 135 | } 136 | ], 137 | "c:showBubbleSize": [ 138 | { 139 | "$": { 140 | "val": "0" 141 | } 142 | } 143 | ] 144 | } 145 | ], 146 | "c:gapWidth": [ 147 | { 148 | "$": { 149 | "val": "150" 150 | } 151 | } 152 | ], 153 | "c:axId": [ 154 | { 155 | "$": { 156 | "val": "2067994824" 157 | } 158 | }, 159 | { 160 | "$": { 161 | "val": "-2074751000" 162 | } 163 | } 164 | ] 165 | } 166 | ], 167 | "c:catAx": [ 168 | { 169 | "c:axId": [ 170 | { 171 | "$": { 172 | "val": "2067994824" 173 | } 174 | } 175 | ], 176 | "c:scaling": [ 177 | { 178 | "c:orientation": [ 179 | { 180 | "$": { 181 | "val": "minMax" 182 | } 183 | } 184 | ] 185 | } 186 | ], 187 | "c:delete": [ 188 | { 189 | "$": { 190 | "val": "0" 191 | } 192 | } 193 | ], 194 | "c:axPos": [ 195 | { 196 | "$": { 197 | "val": "l" 198 | } 199 | } 200 | ], 201 | "c:majorTickMark": [ 202 | { 203 | "$": { 204 | "val": "out" 205 | } 206 | } 207 | ], 208 | "c:minorTickMark": [ 209 | { 210 | "$": { 211 | "val": "none" 212 | } 213 | } 214 | ], 215 | "c:tickLblPos": [ 216 | { 217 | "$": { 218 | "val": "nextTo" 219 | } 220 | } 221 | ], 222 | "c:crossAx": [ 223 | { 224 | "$": { 225 | "val": "-2074751000" 226 | } 227 | } 228 | ], 229 | "c:crosses": [ 230 | { 231 | "$": { 232 | "val": "autoZero" 233 | } 234 | } 235 | ], 236 | "c:auto": [ 237 | { 238 | "$": { 239 | "val": "1" 240 | } 241 | } 242 | ], 243 | "c:lblAlgn": [ 244 | { 245 | "$": { 246 | "val": "ctr" 247 | } 248 | } 249 | ], 250 | "c:lblOffset": [ 251 | { 252 | "$": { 253 | "val": "100" 254 | } 255 | } 256 | ], 257 | "c:noMultiLvlLbl": [ 258 | { 259 | "$": { 260 | "val": "0" 261 | } 262 | } 263 | ] 264 | } 265 | ], 266 | "c:valAx": [ 267 | { 268 | "c:axId": [ 269 | { 270 | "$": { 271 | "val": "-2074751000" 272 | } 273 | } 274 | ], 275 | "c:scaling": [ 276 | { 277 | "c:orientation": [ 278 | { 279 | "$": { 280 | "val": "minMax" 281 | } 282 | } 283 | ] 284 | } 285 | ], 286 | "c:delete": [ 287 | { 288 | "$": { 289 | "val": "0" 290 | } 291 | } 292 | ], 293 | "c:axPos": [ 294 | { 295 | "$": { 296 | "val": "b" 297 | } 298 | } 299 | ], 300 | "c:majorGridlines": [ 301 | "" 302 | ], 303 | "c:numFmt": [ 304 | { 305 | "$": { 306 | "formatCode": "General", 307 | "sourceLinked": "1" 308 | } 309 | } 310 | ], 311 | "c:majorTickMark": [ 312 | { 313 | "$": { 314 | "val": "out" 315 | } 316 | } 317 | ], 318 | "c:minorTickMark": [ 319 | { 320 | "$": { 321 | "val": "none" 322 | } 323 | } 324 | ], 325 | "c:tickLblPos": [ 326 | { 327 | "$": { 328 | "val": "nextTo" 329 | } 330 | } 331 | ], 332 | "c:crossAx": [ 333 | { 334 | "$": { 335 | "val": "2067994824" 336 | } 337 | } 338 | ], 339 | "c:crosses": [ 340 | { 341 | "$": { 342 | "val": "autoZero" 343 | } 344 | } 345 | ], 346 | "c:crossBetween": [ 347 | { 348 | "$": { 349 | "val": "between" 350 | } 351 | } 352 | ] 353 | } 354 | ] 355 | } 356 | ], 357 | "c:legend": [ 358 | { 359 | "c:legendPos": [ 360 | { 361 | "$": { 362 | "val": "r" 363 | } 364 | } 365 | ], 366 | "c:layout": [ 367 | "" 368 | ], 369 | "c:overlay": [ 370 | { 371 | "$": { 372 | "val": "0" 373 | } 374 | } 375 | ] 376 | } 377 | ], 378 | "c:plotVisOnly": [ 379 | { 380 | "$": { 381 | "val": "1" 382 | } 383 | } 384 | ], 385 | "c:dispBlanksAs": [ 386 | { 387 | "$": { 388 | "val": "gap" 389 | } 390 | } 391 | ], 392 | "c:showDLblsOverMax": [ 393 | { 394 | "$": { 395 | "val": "0" 396 | } 397 | } 398 | ] 399 | } 400 | ], 401 | "c:txPr": [ 402 | { 403 | "a:bodyPr": [ 404 | "" 405 | ], 406 | "a:lstStyle": [ 407 | "" 408 | ], 409 | "a:p": [ 410 | { 411 | "a:pPr": [ 412 | { 413 | "a:defRPr": [ 414 | { 415 | "$": { 416 | "sz": "1800" 417 | } 418 | } 419 | ] 420 | } 421 | ], 422 | "a:endParaRPr": [ 423 | { 424 | "$": { 425 | "lang": "en-US" 426 | } 427 | } 428 | ] 429 | } 430 | ] 431 | } 432 | ], 433 | "c:externalData": [ 434 | { 435 | "$": { 436 | "r:id": "rId1" 437 | }, 438 | "c:autoUpdate": [ 439 | { 440 | "$": { 441 | "val": "0" 442 | } 443 | } 444 | ] 445 | } 446 | ] 447 | } 448 | } -------------------------------------------------------------------------------- /lib/fragments/js/chartframe.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "p:graphicFrame": { 3 | "p:nvGraphicFramePr": { 4 | "p:cNvPr": { 5 | "$": { 6 | "id": "5", 7 | "name": "Chart 4" 8 | } 9 | }, 10 | "p:cNvGraphicFramePr": "", 11 | "p:nvPr": { 12 | "p:extLst": { 13 | "p:ext": { 14 | "$": { 15 | "uri": "{D42A27DB-BD31-4B8C-83A1-F6EECF244321}" 16 | }, 17 | "p14:modId": { 18 | "$": { 19 | "xmlns:p14": "http://schemas.microsoft.com/office/powerpoint/2010/main", 20 | "val": "3543180680" 21 | } 22 | } 23 | } 24 | } 25 | } 26 | }, 27 | "p:xfrm": { 28 | "a:off": { 29 | "$": { 30 | "x": "1524000", 31 | "y": "1397000" 32 | } 33 | }, 34 | "a:ext": { 35 | "$": { 36 | "cx": "6096000", 37 | "cy": "4064000" 38 | } 39 | } 40 | }, 41 | "a:graphic": { 42 | "a:graphicData": { 43 | "$": { 44 | "uri": "http://schemas.openxmlformats.org/drawingml/2006/chart" 45 | }, 46 | "c:chart": { 47 | "$": { 48 | "xmlns:c": "http://schemas.openxmlformats.org/drawingml/2006/chart", 49 | "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", 50 | "r:id": "rId2" 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /lib/fragments/xml/chart.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Sheet1!$B$1 30 | 31 | 32 | 33 | Series 1 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Sheet1!$A$2:$A$5 42 | 43 | 44 | 45 | Category 1 46 | 47 | 48 | Category 2 49 | 50 | 51 | Category 3 52 | 53 | 54 | Category 4 55 | 56 | 57 | 58 | 59 | 60 | 61 | Sheet1!$B$2:$B$5 62 | 63 | General 64 | 65 | 66 | 4.3 67 | 68 | 69 | 2.5 70 | 71 | 72 | 3.5 73 | 74 | 75 | 4.5 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | Sheet1!$C$1 87 | 88 | 89 | 90 | Series 2 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | Sheet1!$A$2:$A$5 99 | 100 | 101 | 102 | Category 1 103 | 104 | 105 | Category 2 106 | 107 | 108 | Category 3 109 | 110 | 111 | Category 4 112 | 113 | 114 | 115 | 116 | 117 | 118 | Sheet1!$C$2:$C$5 119 | 120 | General 121 | 122 | 123 | 2.4 124 | 125 | 126 | 4.4 127 | 128 | 129 | 1.8 130 | 131 | 132 | 2.8 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | Sheet1!$D$1 144 | 145 | 146 | 147 | Series 3 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | Sheet1!$A$2:$A$5 156 | 157 | 158 | 159 | Category 1 160 | 161 | 162 | Category 2 163 | 164 | 165 | Category 3 166 | 167 | 168 | Category 4 169 | 170 | 171 | 172 | 173 | 174 | 175 | Sheet1!$D$2:$D$5 176 | 177 | General 178 | 179 | 180 | 2.0 181 | 182 | 183 | 2.0 184 | 185 | 186 | 3.0 187 | 188 | 189 | 5.0 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /lib/fragments/xml/chartframe.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /lib/msexcel-builder.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | 3 | /* 4 | MS Excel 2007 Creater v0.0.1 5 | Author : chuanyi.zheng@gmail.com 6 | History: 2012/11/07 first created 7 | */ 8 | 9 | (function() { 10 | var ContentTypes, DocPropsApp, JSZip, SharedStrings, Sheet, Style, Workbook, XlRels, XlWorkbook, a, baseXl, opt, tool, xml, 11 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 12 | 13 | JSZip = require('jszip'); 14 | 15 | xml = require('xmlbuilder'); 16 | 17 | a = 4; 18 | 19 | tool = { 20 | i2a: function(i) { 21 | return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ123'.charAt(i - 1); 22 | } 23 | }; 24 | 25 | opt = { 26 | tmpl_path: __dirname 27 | }; 28 | 29 | ContentTypes = (function() { 30 | function ContentTypes(book) { 31 | this.book = book; 32 | } 33 | 34 | ContentTypes.prototype.toxml = function() { 35 | var i, types, _i, _ref; 36 | types = xml.create('Types', { 37 | version: '1.0', 38 | encoding: 'UTF-8', 39 | standalone: true 40 | }); 41 | types.att('xmlns', 'http://schemas.openxmlformats.org/package/2006/content-types'); 42 | types.ele('Override', { 43 | PartName: '/xl/theme/theme1.xml', 44 | ContentType: 'application/vnd.openxmlformats-officedocument.theme+xml' 45 | }); 46 | types.ele('Override', { 47 | PartName: '/xl/styles.xml', 48 | ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml' 49 | }); 50 | types.ele('Default', { 51 | Extension: 'rels', 52 | ContentType: 'application/vnd.openxmlformats-package.relationships+xml' 53 | }); 54 | types.ele('Default', { 55 | Extension: 'xml', 56 | ContentType: 'application/xml' 57 | }); 58 | types.ele('Override', { 59 | PartName: '/xl/workbook.xml', 60 | ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml' 61 | }); 62 | types.ele('Override', { 63 | PartName: '/docProps/app.xml', 64 | ContentType: 'application/vnd.openxmlformats-officedocument.extended-properties+xml' 65 | }); 66 | for (i = _i = 1, _ref = this.book.sheets.length; 1 <= _ref ? _i <= _ref : _i >= _ref; i = 1 <= _ref ? ++_i : --_i) { 67 | types.ele('Override', { 68 | PartName: '/xl/worksheets/sheet' + i + '.xml', 69 | ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml' 70 | }); 71 | } 72 | types.ele('Override', { 73 | PartName: '/xl/sharedStrings.xml', 74 | ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml' 75 | }); 76 | types.ele('Override', { 77 | PartName: '/docProps/core.xml', 78 | ContentType: 'application/vnd.openxmlformats-package.core-properties+xml' 79 | }); 80 | return types.end(); 81 | }; 82 | 83 | return ContentTypes; 84 | 85 | })(); 86 | 87 | DocPropsApp = (function() { 88 | function DocPropsApp(book) { 89 | this.book = book; 90 | } 91 | 92 | DocPropsApp.prototype.toxml = function() { 93 | var i, props, tmp, _i, _ref; 94 | props = xml.create('Properties', { 95 | version: '1.0', 96 | encoding: 'UTF-8', 97 | standalone: true 98 | }); 99 | props.att('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'); 100 | props.att('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); 101 | props.ele('Application', 'Microsoft Excel'); 102 | props.ele('DocSecurity', '0'); 103 | props.ele('ScaleCrop', 'false'); 104 | tmp = props.ele('HeadingPairs').ele('vt:vector', { 105 | size: 2, 106 | baseType: 'variant' 107 | }); 108 | tmp.ele('vt:variant').ele('vt:lpstr', '工作表'); 109 | tmp.ele('vt:variant').ele('vt:i4', '' + this.book.sheets.length); 110 | tmp = props.ele('TitlesOfParts').ele('vt:vector', { 111 | size: this.book.sheets.length, 112 | baseType: 'lpstr' 113 | }); 114 | for (i = _i = 1, _ref = this.book.sheets.length; 1 <= _ref ? _i <= _ref : _i >= _ref; i = 1 <= _ref ? ++_i : --_i) { 115 | tmp.ele('vt:lpstr', this.book.sheets[i - 1].name); 116 | } 117 | props.ele('Company'); 118 | props.ele('LinksUpToDate', 'false'); 119 | props.ele('SharedDoc', 'false'); 120 | props.ele('HyperlinksChanged', 'false'); 121 | props.ele('AppVersion', '12.0000'); 122 | return props.end(); 123 | }; 124 | 125 | return DocPropsApp; 126 | 127 | })(); 128 | 129 | XlWorkbook = (function() { 130 | function XlWorkbook(book) { 131 | this.book = book; 132 | } 133 | 134 | XlWorkbook.prototype.toxml = function() { 135 | var i, tmp, wb, _i, _ref; 136 | wb = xml.create('workbook', { 137 | version: '1.0', 138 | encoding: 'UTF-8', 139 | standalone: true 140 | }); 141 | wb.att('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); 142 | wb.att('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); 143 | wb.ele('fileVersion ', { 144 | appName: 'xl', 145 | lastEdited: '4', 146 | lowestEdited: '4', 147 | rupBuild: '4505' 148 | }); 149 | wb.ele('workbookPr', { 150 | filterPrivacy: '1', 151 | defaultThemeVersion: '124226' 152 | }); 153 | wb.ele('bookViews').ele('workbookView ', { 154 | xWindow: '0', 155 | yWindow: '90', 156 | windowWidth: '19200', 157 | windowHeight: '11640' 158 | }); 159 | tmp = wb.ele('sheets'); 160 | for (i = _i = 1, _ref = this.book.sheets.length; 1 <= _ref ? _i <= _ref : _i >= _ref; i = 1 <= _ref ? ++_i : --_i) { 161 | tmp.ele('sheet', { 162 | name: this.book.sheets[i - 1].name, 163 | sheetId: '' + i, 164 | 'r:id': 'rId' + i 165 | }); 166 | } 167 | wb.ele('calcPr', { 168 | calcId: '124519' 169 | }); 170 | return wb.end(); 171 | }; 172 | 173 | return XlWorkbook; 174 | 175 | })(); 176 | 177 | XlRels = (function() { 178 | function XlRels(book) { 179 | this.book = book; 180 | } 181 | 182 | XlRels.prototype.toxml = function() { 183 | var i, rs, _i, _ref; 184 | rs = xml.create('Relationships', { 185 | version: '1.0', 186 | encoding: 'UTF-8', 187 | standalone: true 188 | }); 189 | rs.att('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); 190 | for (i = _i = 1, _ref = this.book.sheets.length; 1 <= _ref ? _i <= _ref : _i >= _ref; i = 1 <= _ref ? ++_i : --_i) { 191 | rs.ele('Relationship', { 192 | Id: 'rId' + i, 193 | Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet', 194 | Target: 'worksheets/sheet' + i + '.xml' 195 | }); 196 | } 197 | rs.ele('Relationship', { 198 | Id: 'rId' + (this.book.sheets.length + 1), 199 | Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme', 200 | Target: 'theme/theme1.xml' 201 | }); 202 | rs.ele('Relationship', { 203 | Id: 'rId' + (this.book.sheets.length + 2), 204 | Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles', 205 | Target: 'styles.xml' 206 | }); 207 | rs.ele('Relationship', { 208 | Id: 'rId' + (this.book.sheets.length + 3), 209 | Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings', 210 | Target: 'sharedStrings.xml' 211 | }); 212 | return rs.end(); 213 | }; 214 | 215 | return XlRels; 216 | 217 | })(); 218 | 219 | SharedStrings = (function() { 220 | function SharedStrings() { 221 | this.cache = {}; 222 | this.arr = []; 223 | } 224 | 225 | SharedStrings.prototype.str2id = function(s) { 226 | var id; 227 | id = this.cache[s]; 228 | if (id) { 229 | return id; 230 | } else { 231 | this.arr.push(s); 232 | this.cache[s] = this.arr.length; 233 | return this.arr.length; 234 | } 235 | }; 236 | 237 | SharedStrings.prototype.toxml = function() { 238 | var i, si, sst, _i, _ref; 239 | sst = xml.create('sst', { 240 | version: '1.0', 241 | encoding: 'UTF-8', 242 | standalone: true 243 | }); 244 | sst.att('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); 245 | sst.att('count', '' + this.arr.length); 246 | sst.att('uniqueCount', '' + this.arr.length); 247 | for (i = _i = 0, _ref = this.arr.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { 248 | si = sst.ele('si'); 249 | si.ele('t', this.arr[i]); 250 | si.ele('phoneticPr', { 251 | fontId: 1, 252 | type: 'noConversion' 253 | }); 254 | } 255 | return sst.end(); 256 | }; 257 | 258 | return SharedStrings; 259 | 260 | })(); 261 | 262 | Sheet = (function() { 263 | function Sheet(book, name, cols, rows) { 264 | var i, j, _i, _j, _ref, _ref1; 265 | this.book = book; 266 | this.name = name; 267 | this.cols = cols; 268 | this.rows = rows; 269 | this.data = {}; 270 | for (i = _i = 1, _ref = this.rows; 1 <= _ref ? _i <= _ref : _i >= _ref; i = 1 <= _ref ? ++_i : --_i) { 271 | this.data[i] = {}; 272 | for (j = _j = 1, _ref1 = this.cols; 1 <= _ref1 ? _j <= _ref1 : _j >= _ref1; j = 1 <= _ref1 ? ++_j : --_j) { 273 | this.data[i][j] = { 274 | v: 0 275 | }; 276 | } 277 | } 278 | this.merges = []; 279 | this.col_wd = []; 280 | this.row_ht = {}; 281 | this.styles = {}; 282 | } 283 | 284 | Sheet.prototype.set = function(col, row, str) { 285 | if ((str != null) && str !== '') { 286 | return this.data[row][col].v = this.book.ss.str2id('' + str); 287 | } 288 | }; 289 | 290 | Sheet.prototype.merge = function(from_cell, to_cell) { 291 | return this.merges.push({ 292 | from: from_cell, 293 | to: to_cell 294 | }); 295 | }; 296 | 297 | Sheet.prototype.width = function(col, wd) { 298 | return this.col_wd.push({ 299 | c: col, 300 | cw: wd 301 | }); 302 | }; 303 | 304 | Sheet.prototype.height = function(row, ht) { 305 | return this.row_ht[row] = ht; 306 | }; 307 | 308 | Sheet.prototype.font = function(col, row, font_s) { 309 | return this.styles['font_' + col + '_' + row] = this.book.st.font2id(font_s); 310 | }; 311 | 312 | Sheet.prototype.fill = function(col, row, fill_s) { 313 | return this.styles['fill_' + col + '_' + row] = this.book.st.fill2id(fill_s); 314 | }; 315 | 316 | Sheet.prototype.border = function(col, row, bder_s) { 317 | return this.styles['bder_' + col + '_' + row] = this.book.st.bder2id(bder_s); 318 | }; 319 | 320 | Sheet.prototype.align = function(col, row, align_s) { 321 | return this.styles['algn_' + col + '_' + row] = align_s; 322 | }; 323 | 324 | Sheet.prototype.valign = function(col, row, valign_s) { 325 | return this.styles['valgn_' + col + '_' + row] = valign_s; 326 | }; 327 | 328 | Sheet.prototype.rotate = function(col, row, textRotation) { 329 | return this.styles['rotate_' + col + '_' + row] = textRotation; 330 | }; 331 | 332 | Sheet.prototype.wrap = function(col, row, wrap_s) { 333 | return this.styles['wrap_' + col + '_' + row] = wrap_s; 334 | }; 335 | 336 | Sheet.prototype.style_id = function(col, row) { 337 | var id, inx, style; 338 | inx = '_' + col + '_' + row; 339 | style = { 340 | font_id: this.styles['font' + inx], 341 | fill_id: this.styles['fill' + inx], 342 | bder_id: this.styles['bder' + inx], 343 | align: this.styles['algn' + inx], 344 | valign: this.styles['valgn' + inx], 345 | rotate: this.styles['rotate' + inx], 346 | wrap: this.styles['wrap' + inx] 347 | }; 348 | id = this.book.st.style2id(style); 349 | return id; 350 | }; 351 | 352 | Sheet.prototype.toxml = function() { 353 | var c, cols, cw, ht, i, ix, j, m, mc, r, sd, sid, ws, _i, _j, _k, _l, _len, _len1, _ref, _ref1, _ref2, _ref3; 354 | ws = xml.create('worksheet', { 355 | version: '1.0', 356 | encoding: 'UTF-8', 357 | standalone: true 358 | }); 359 | ws.att('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); 360 | ws.att('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); 361 | ws.ele('dimension', { 362 | ref: 'A1' 363 | }); 364 | ws.ele('sheetViews').ele('sheetView', { 365 | workbookViewId: '0' 366 | }); 367 | ws.ele('sheetFormatPr', { 368 | defaultRowHeight: '13.5' 369 | }); 370 | if (this.col_wd.length > 0) { 371 | cols = ws.ele('cols'); 372 | _ref = this.col_wd; 373 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 374 | cw = _ref[_i]; 375 | cols.ele('col', { 376 | min: '' + cw.c, 377 | max: '' + cw.c, 378 | width: cw.cw, 379 | customWidth: '1' 380 | }); 381 | } 382 | } 383 | sd = ws.ele('sheetData'); 384 | for (i = _j = 1, _ref1 = this.rows; 1 <= _ref1 ? _j <= _ref1 : _j >= _ref1; i = 1 <= _ref1 ? ++_j : --_j) { 385 | r = sd.ele('row', { 386 | r: '' + i, 387 | spans: '1:' + this.cols 388 | }); 389 | ht = this.row_ht[i]; 390 | if (ht) { 391 | r.att('ht', ht); 392 | r.att('customHeight', '1'); 393 | } 394 | for (j = _k = 1, _ref2 = this.cols; 1 <= _ref2 ? _k <= _ref2 : _k >= _ref2; j = 1 <= _ref2 ? ++_k : --_k) { 395 | ix = this.data[i][j]; 396 | sid = this.style_id(j, i); 397 | if ((ix.v !== 0) || (sid !== 1)) { 398 | c = r.ele('c', { 399 | r: '' + tool.i2a(j) + i 400 | }); 401 | if (sid !== 1) { 402 | c.att('s', '' + (sid - 1)); 403 | } 404 | if (ix.v !== 0) { 405 | c.att('t', 's'); 406 | c.ele('v', '' + (ix.v - 1)); 407 | } 408 | } 409 | } 410 | } 411 | if (this.merges.length > 0) { 412 | mc = ws.ele('mergeCells', { 413 | count: this.merges.length 414 | }); 415 | _ref3 = this.merges; 416 | for (_l = 0, _len1 = _ref3.length; _l < _len1; _l++) { 417 | m = _ref3[_l]; 418 | mc.ele('mergeCell', { 419 | ref: '' + tool.i2a(m.from.col) + m.from.row + ':' + tool.i2a(m.to.col) + m.to.row 420 | }); 421 | } 422 | } 423 | ws.ele('phoneticPr', { 424 | fontId: '1', 425 | type: 'noConversion' 426 | }); 427 | ws.ele('pageMargins', { 428 | left: '0.7', 429 | right: '0.7', 430 | top: '0.75', 431 | bottom: '0.75', 432 | header: '0.3', 433 | footer: '0.3' 434 | }); 435 | ws.ele('pageSetup', { 436 | paperSize: '9', 437 | orientation: 'portrait', 438 | horizontalDpi: '200', 439 | verticalDpi: '200' 440 | }); 441 | return ws.end(); 442 | }; 443 | 444 | return Sheet; 445 | 446 | })(); 447 | 448 | Style = (function() { 449 | function Style(book) { 450 | this.book = book; 451 | this.cache = {}; 452 | this.mfonts = []; 453 | this.mfills = []; 454 | this.mbders = []; 455 | this.mstyle = []; 456 | this.with_default(); 457 | } 458 | 459 | Style.prototype.with_default = function() { 460 | this.def_font_id = this.font2id(null); 461 | this.def_fill_id = this.fill2id(null); 462 | this.def_bder_id = this.bder2id(null); 463 | this.def_align = '-'; 464 | this.def_valign = '-'; 465 | this.def_rotate = '-'; 466 | this.def_wrap = '-'; 467 | return this.def_style_id = this.style2id({ 468 | font_id: this.def_font_id, 469 | fill_id: this.def_fill_id, 470 | bder_id: this.def_bder_id, 471 | align: this.def_align, 472 | valign: this.def_valign, 473 | rotate: this.def_rotate 474 | }); 475 | }; 476 | 477 | Style.prototype.font2id = function(font) { 478 | var id, k; 479 | font || (font = {}); 480 | font.bold || (font.bold = '-'); 481 | font.iter || (font.iter = '-'); 482 | font.sz || (font.sz = '11'); 483 | font.color || (font.color = '-'); 484 | font.name || (font.name = '宋体'); 485 | font.scheme || (font.scheme = 'minor'); 486 | font.family || (font.family = '2'); 487 | k = 'font_' + font.bold + font.iter + font.sz + font.color + font.name + font.scheme + font.family; 488 | id = this.cache[k]; 489 | if (id) { 490 | return id; 491 | } else { 492 | this.mfonts.push(font); 493 | this.cache[k] = this.mfonts.length; 494 | return this.mfonts.length; 495 | } 496 | }; 497 | 498 | Style.prototype.fill2id = function(fill) { 499 | var id, k; 500 | fill || (fill = {}); 501 | fill.type || (fill.type = 'none'); 502 | fill.bgColor || (fill.bgColor = '-'); 503 | fill.fgColor || (fill.fgColor = '-'); 504 | k = 'fill_' + fill.type + fill.bgColor + fill.fgColor; 505 | id = this.cache[k]; 506 | if (id) { 507 | return id; 508 | } else { 509 | this.mfills.push(fill); 510 | this.cache[k] = this.mfills.length; 511 | return this.mfills.length; 512 | } 513 | }; 514 | 515 | Style.prototype.bder2id = function(bder) { 516 | var id, k; 517 | bder || (bder = {}); 518 | bder.left || (bder.left = '-'); 519 | bder.right || (bder.right = '-'); 520 | bder.top || (bder.top = '-'); 521 | bder.bottom || (bder.bottom = '-'); 522 | k = 'bder_' + bder.left + '_' + bder.right + '_' + bder.top + '_' + bder.bottom; 523 | id = this.cache[k]; 524 | if (id) { 525 | return id; 526 | } else { 527 | this.mbders.push(bder); 528 | this.cache[k] = this.mbders.length; 529 | return this.mbders.length; 530 | } 531 | }; 532 | 533 | Style.prototype.style2id = function(style) { 534 | var id, k; 535 | style.align || (style.align = this.def_align); 536 | style.valign || (style.valign = this.def_valign); 537 | style.rotate || (style.rotate = this.def_rotate); 538 | style.wrap || (style.wrap = this.def_wrap); 539 | style.font_id || (style.font_id = this.def_font_id); 540 | style.fill_id || (style.fill_id = this.def_fill_id); 541 | style.bder_id || (style.bder_id = this.def_bder_id); 542 | k = 's_' + style.font_id + '_' + style.fill_id + '_' + style.bder_id + '_' + style.align + '_' + style.valign + '_' + style.wrap + '_' + style.rotate; 543 | id = this.cache[k]; 544 | if (id) { 545 | return id; 546 | } else { 547 | this.mstyle.push(style); 548 | this.cache[k] = this.mstyle.length; 549 | return this.mstyle.length; 550 | } 551 | }; 552 | 553 | Style.prototype.toxml = function() { 554 | var bders, cs, e, ea, es, fills, fonts, o, ss, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3; 555 | ss = xml.create('styleSheet', { 556 | version: '1.0', 557 | encoding: 'UTF-8', 558 | standalone: true 559 | }); 560 | ss.att('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); 561 | fonts = ss.ele('fonts', { 562 | count: this.mfonts.length 563 | }); 564 | _ref = this.mfonts; 565 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 566 | o = _ref[_i]; 567 | e = fonts.ele('font'); 568 | if (o.bold !== '-') { 569 | e.ele('b'); 570 | } 571 | if (o.iter !== '-') { 572 | e.ele('i'); 573 | } 574 | e.ele('sz', { 575 | val: o.sz 576 | }); 577 | if (o.color !== '-') { 578 | e.ele('color', { 579 | theme: o.color 580 | }); 581 | } 582 | e.ele('name', { 583 | val: o.name 584 | }); 585 | e.ele('family', { 586 | val: o.family 587 | }); 588 | e.ele('charset', { 589 | val: '134' 590 | }); 591 | if (o.scheme !== '-') { 592 | e.ele('scheme', { 593 | val: 'minor' 594 | }); 595 | } 596 | } 597 | fills = ss.ele('fills', { 598 | count: this.mfills.length 599 | }); 600 | _ref1 = this.mfills; 601 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 602 | o = _ref1[_j]; 603 | e = fills.ele('fill'); 604 | es = e.ele('patternFill', { 605 | patternType: o.type 606 | }); 607 | if (o.fgColor !== '-') { 608 | es.ele('fgColor', { 609 | theme: '8', 610 | tint: '0.79998168889431442' 611 | }); 612 | } 613 | if (o.bgColor !== '-') { 614 | es.ele('bgColor', { 615 | indexed: o.bgColor 616 | }); 617 | } 618 | } 619 | bders = ss.ele('borders', { 620 | count: this.mbders.length 621 | }); 622 | _ref2 = this.mbders; 623 | for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { 624 | o = _ref2[_k]; 625 | e = bders.ele('border'); 626 | if (o.left !== '-') { 627 | e.ele('left', { 628 | style: o.left 629 | }).ele('color', { 630 | auto: '1' 631 | }); 632 | } else { 633 | e.ele('left'); 634 | } 635 | if (o.right !== '-') { 636 | e.ele('right', { 637 | style: o.right 638 | }).ele('color', { 639 | auto: '1' 640 | }); 641 | } else { 642 | e.ele('right'); 643 | } 644 | if (o.top !== '-') { 645 | e.ele('top', { 646 | style: o.top 647 | }).ele('color', { 648 | auto: '1' 649 | }); 650 | } else { 651 | e.ele('top'); 652 | } 653 | if (o.bottom !== '-') { 654 | e.ele('bottom', { 655 | style: o.bottom 656 | }).ele('color', { 657 | auto: '1' 658 | }); 659 | } else { 660 | e.ele('bottom'); 661 | } 662 | e.ele('diagonal'); 663 | } 664 | ss.ele('cellStyleXfs', { 665 | count: '1' 666 | }).ele('xf', { 667 | numFmtId: '0', 668 | fontId: '0', 669 | fillId: '0', 670 | borderId: '0' 671 | }).ele('alignment', { 672 | vertical: 'center' 673 | }); 674 | cs = ss.ele('cellXfs', { 675 | count: this.mstyle.length 676 | }); 677 | _ref3 = this.mstyle; 678 | for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { 679 | o = _ref3[_l]; 680 | e = cs.ele('xf', { 681 | numFmtId: '0', 682 | fontId: o.font_id - 1, 683 | fillId: o.fill_id - 1, 684 | borderId: o.bder_id - 1, 685 | xfId: '0' 686 | }); 687 | if (o.font_id !== 1) { 688 | e.att('applyFont', '1'); 689 | } 690 | if (o.fill_id !== 1) { 691 | e.att('applyFill', '1'); 692 | } 693 | if (o.bder_id !== 1) { 694 | e.att('applyBorder', '1'); 695 | } 696 | if (o.align !== '-' || o.valign !== '-' || o.wrap !== '-') { 697 | e.att('applyAlignment', '1'); 698 | ea = e.ele('alignment', { 699 | textRotation: (o.rotate === '-' ? '0' : o.rotate), 700 | horizontal: (o.align === '-' ? 'left' : o.align), 701 | vertical: (o.valign === '-' ? 'top' : o.valign) 702 | }); 703 | if (o.wrap !== '-') { 704 | ea.att('wrapText', '1'); 705 | } 706 | } 707 | } 708 | ss.ele('cellStyles', { 709 | count: '1' 710 | }).ele('cellStyle', { 711 | name: '常规', 712 | xfId: '0', 713 | builtinId: '0' 714 | }); 715 | ss.ele('dxfs', { 716 | count: '0' 717 | }); 718 | ss.ele('tableStyles', { 719 | count: '0', 720 | defaultTableStyle: 'TableStyleMedium9', 721 | defaultPivotStyle: 'PivotStyleLight16' 722 | }); 723 | return ss.end(); 724 | }; 725 | 726 | return Style; 727 | 728 | })(); 729 | 730 | Workbook = (function() { 731 | function Workbook() { 732 | this.generate = __bind(this.generate, this); 733 | this.id = '' + parseInt(Math.random() * 9999999); 734 | this.sheets = []; 735 | this.ss = new SharedStrings; 736 | this.ct = new ContentTypes(this); 737 | this.da = new DocPropsApp(this); 738 | this.wb = new XlWorkbook(this); 739 | this.re = new XlRels(this); 740 | this.st = new Style(this); 741 | } 742 | 743 | Workbook.prototype.createSheet = function(name, cols, rows) { 744 | var sheet; 745 | sheet = new Sheet(this, name, cols, rows); 746 | this.sheets.push(sheet); 747 | return sheet; 748 | }; 749 | 750 | Workbook.prototype.generate = function(cb) { 751 | var i, key, zip, _i, _ref; 752 | zip = new JSZip; 753 | for (key in baseXl) { 754 | zip.file(key, baseXl[key]); 755 | } 756 | zip.file('[Content_Types].xml', this.ct.toxml()); 757 | zip.file('docProps/app.xml', this.da.toxml()); 758 | zip.file('xl/workbook.xml', this.wb.toxml()); 759 | zip.file('xl/sharedStrings.xml', this.ss.toxml()); 760 | zip.file('xl/_rels/workbook.xml.rels', this.re.toxml()); 761 | for (i = _i = 0, _ref = this.sheets.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { 762 | zip.file('xl/worksheets/sheet' + (i + 1) + '.xml', this.sheets[i].toxml()); 763 | } 764 | zip.file('xl/styles.xml', this.st.toxml()); 765 | return cb(null, zip); 766 | }; 767 | 768 | return Workbook; 769 | 770 | })(); 771 | 772 | module.exports = { 773 | createWorkbook: function() { 774 | return new Workbook(); 775 | } 776 | }; 777 | 778 | baseXl = { 779 | '_rels/.rels': '', 780 | 'docProps/core.xml': 'Administrator2006-09-13T11:21:51Z2006-09-13T11:21:55Z', 781 | 'xl/theme/theme1.xml': '', 782 | 'xl/styles.xml': '' 783 | }; 784 | 785 | }).call(this); 786 | 787 | //# sourceMappingURL=msexcel-builder.js.map 788 | -------------------------------------------------------------------------------- /lib/officechart.js: -------------------------------------------------------------------------------- 1 | /// @author vtloc 2 | /// @date 2014Jan09 3 | /// @author GradualStudent 4 | /// @date 2015jan06 5 | /// This module's purpose is to transform 6 | /// 7 | var _ = require('lodash'); // replacing underscore to get merge function 8 | var fs = require('fs'); 9 | var _chartSpecs = require('./charts'); 10 | 11 | 12 | function OfficeChart(chartInfo) { 13 | 14 | if (chartInfo instanceof OfficeChart) { 15 | return chartInfo; 16 | } 17 | 18 | return { 19 | 20 | chartSpec: null, // Javascript object that represents the XML tree for the PowerPoint chart 21 | 22 | toJSON: function () { 23 | return this.chartSpec; 24 | }, 25 | 26 | getClass: function() { return "OfficeChart"; }, 27 | 28 | /// 29 | /// @brief Create XML representation of chart object 30 | /// @param[chartInfo] object 31 | /// { 32 | /// title: 'eSurvey chart', 33 | /// data: [ // array of series 34 | /// { 35 | /// name: 'Income', 36 | /// labels: ['2005', '2006', '2007', '2008', '2009'], 37 | /// values: [23.5, 26.2, 30.1, 29.5, 24.6], 38 | /// color: 'ff0000' 39 | /// } 40 | /// ], 41 | /// overlap: "0", 42 | /// gapWidth: "150" 43 | /// 44 | /// } 45 | 46 | initialize: function (chartInfo) { 47 | 48 | if (chartInfo.getClass && chartInfo.getClass()== 'OfficeChart') { 49 | return chartInfo; 50 | } 51 | 52 | // overlap ["50"] is handled as an option within the chartbase 53 | // gapWidth ["150"] is handled as an option within the chartbase 54 | // valAxisCrossAtMaxCategory [true|false] is handled as an option within the chart base 55 | // catAxisReverseOrder [true|false] is handled as an option within the chart base 56 | 57 | this.chartSpec = OfficeChart.getChartBase(chartInfo); // get foundation XML for the chart type 58 | 59 | // Below are methods for handling options with more complex XML to mix in 60 | this.setData(chartInfo['data']); 61 | this.setTitle(chartInfo.title || chartInfo.name); 62 | this.setValAxisTitle(chartInfo.valAxisTitle); 63 | this.setCatAxisTitle(chartInfo.catAxisTitle); 64 | this.setValAxisNumFmt(chartInfo.valAxisNumFmt); 65 | this.setValAxisScale(chartInfo.valAxisMinValue, chartInfo.valAxisMaxValue); 66 | this.setTextSize(chartInfo.fontSize); 67 | this.mergeChartXml(chartInfo.xml); 68 | this.setValAxisMajorGridlines(chartInfo.valAxisMajorGridlines); 69 | this.setValAxisMinorGridlines(chartInfo.valAxisMinorGridlines); 70 | 71 | return this; 72 | }, 73 | 74 | setTextSize: function(textSize) { 75 | if (textSize !== undefined) { 76 | var textRef = this._text(textSize); 77 | _.merge(this.chartSpec['c:chartSpace'], textRef); 78 | } 79 | }, 80 | 81 | setTitle: function (title) { 82 | if (title !== undefined) { 83 | var titleRef = this._title(chartInfo.title || chartInfo.name); 84 | _.merge(this.chartSpec['c:chartSpace']['c:chart'], titleRef) 85 | } 86 | }, 87 | 88 | setValAxisTitle: function (title) { 89 | if (title) { 90 | var titleRef = this._title(title); 91 | _.merge(this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:valAx'], titleRef); 92 | } 93 | }, 94 | 95 | setCatAxisTitle: function (title) { 96 | if (title) { 97 | var titleRef = this._title(title); 98 | _.merge(this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:catAx'], titleRef); 99 | } 100 | }, 101 | 102 | setValAxisNumFmt: function (format) { 103 | if (format !== undefined) { 104 | var numFmtRef = this._numFmt(format); 105 | _.merge(this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:valAx'], numFmtRef) 106 | } 107 | }, 108 | 109 | setValAxisScale: function (min, max) { 110 | if (min!==undefined || max!== undefined) { 111 | var scalingRef = this._scaling(min, max); 112 | _.merge(this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:valAx'], scalingRef); 113 | } 114 | }, 115 | 116 | mergeChartXml: function (xml) { 117 | if (xml !== undefined) { 118 | _.merge(this.chartSpec['c:chartSpace'], xml); 119 | } 120 | }, 121 | 122 | setValAxisMajorGridlines: function(boolean) { 123 | if (boolean) { 124 | this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:valAx']['c:majorGridlines'] = {}; 125 | } 126 | }, 127 | setValAxisMinorGridlines: function(boolean) { 128 | if (boolean) { 129 | this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:valAx']['c:minorGridlines'] = {}; 130 | } 131 | }, 132 | 133 | 134 | setData: function (data) { 135 | 136 | if (data) { 137 | this.data = data; 138 | 139 | // Mix in data series 140 | var seriesDataRef; 141 | if (this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:barChart']) { 142 | seriesDataRef = this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:barChart']['#list']; 143 | } 144 | else if (this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:pieChart']) { 145 | seriesDataRef = this.chartSpec['c:chartSpace']['c:chart']['c:plotArea']['c:pieChart']['#list']; 146 | } 147 | else throw new Error("Can't add data to chart that is not initialized or not a recognized type") 148 | 149 | // the barChart/pieChart objects has other attributes too, so we push the series data on, not replace it 150 | if (chartInfo['data']) { 151 | var seriesData = this.getSeriesRef(chartInfo['data']); 152 | for (var i = 0; i < seriesData.length; i++) { 153 | seriesDataRef.push(seriesData[i]) 154 | } 155 | } 156 | } 157 | return this; 158 | }, 159 | 160 | 161 | /// 162 | /// @brief Transform an array of string into an office's compliance structure 163 | /// 164 | /// @param[in] region String 165 | /// The reference cell of the string, for example: $A$1 166 | /// @param[in] stringArr 167 | /// An array of string, for example: ['foo', 'bar'] 168 | /// 169 | _strRef: function (region, stringArr) { 170 | var obj = { 171 | 'c:strRef': { 172 | 'c:f': region, 173 | 'c:strCache': function () { 174 | var result = {}; 175 | result['c:ptCount'] = { '@val': stringArr.length }; 176 | result['#list'] = []; 177 | for (var i = 0; i < stringArr.length; i++) { 178 | result['#list'].push({'c:pt': { '@idx': i, 'c:v': stringArr[i] }}); 179 | } 180 | return result; 181 | }() 182 | } 183 | }; 184 | 185 | return obj; 186 | }, 187 | 188 | /// 189 | /// @brief Transform an array of numbers into an office's compliance structure 190 | /// 191 | /// @param[in] region String 192 | /// The reference cell of the string, for example: $A$1 193 | /// @param[in] numArr 194 | /// An array of numArr, for example: [4, 7, 8] 195 | /// @param[in] formatCode 196 | /// A string describe the number's format. Example: General 197 | /// 198 | _numRef: function (region, numArr, formatCode) { 199 | var obj = { 200 | 'c:numRef': { 201 | 'c:f': region, 202 | 'c:numCache': { 203 | 'c:formatCode': formatCode, 204 | 'c:ptCount': {'@val': '' + numArr.length}, 205 | '#list': function () { 206 | result = []; 207 | for (var i = 0; i < numArr.length; i++) { 208 | result.push({'c:pt': { '@idx': i, 'c:v': numArr[i].toString() }}); 209 | } 210 | 211 | return result; 212 | } 213 | } 214 | } 215 | }; 216 | 217 | return obj; 218 | }, 219 | 220 | _numFmt: function (formatCode) { 221 | return { 222 | "c:numFmt": { 223 | "@formatCode": formatCode ? formatCode : "General", 224 | "@sourceLinked": formatCode ? "0" : "1" 225 | } 226 | } 227 | }, 228 | 229 | /// 230 | /// @brief Transform an array of string into an office's compliance structure 231 | /// 232 | /// @param[in] colorArr 233 | /// An array of colorArr, for example: ['ff0000', '00ff00', '0000ff'] 234 | /// 235 | _colorRef: function (colorArr) { 236 | var arr = []; 237 | for (var i = 0; i < colorArr.length; i++) { 238 | arr.push({ 239 | 'c:dPt': { 240 | 'c:idx': {'@val': i}, 241 | 'c:bubble3D': {'@val': 0}, 242 | 'c:spPr': { 243 | 'a:solidFill': { 244 | 'a:srgbClr': {'@val': colorArr[i].toString()} 245 | } 246 | } 247 | } 248 | }); 249 | } 250 | 251 | return arr; 252 | }, 253 | 254 | /// 255 | /// @brief Transform an array of string into an office's compliance structure 256 | /// 257 | /// @param[in] row int 258 | /// Row index. 259 | /// @param[in] col int 260 | /// Col index. 261 | /// @param[in] isRowAbsolute boolean 262 | /// Will add $ into cell's address if this parameter is true. 263 | /// @param[in] isColAbsolute boolean 264 | /// Will add $ into cell's address if this parameter is true. 265 | /// 266 | _rowColToSheetAddress: function (row, col, isRowAbsolute, isColAbsolute) { 267 | var address = ""; 268 | 269 | if (isColAbsolute) 270 | address += '$'; 271 | 272 | // these lines of code will transform the number 1-26 into A->Z 273 | // used in excel's cell's coordination 274 | while (col > 0) { 275 | var num = col % 26; 276 | col = (col - num ) / 26; 277 | address += String.fromCharCode(65 + num - 1); 278 | } 279 | 280 | if (isRowAbsolute) 281 | address += '$'; 282 | 283 | address += row; 284 | 285 | return address; 286 | }, 287 | 288 | /// @brief returns XML snippet for a chart dataseries 289 | _ser: function (serie, i) { 290 | var rc2a = this._rowColToSheetAddress; // shortcut 291 | 292 | var serieData = { 293 | 'c:ser': { 294 | 'c:idx': {'@val': i}, 295 | 'c:order': {'@val': i}, 296 | 'c:tx': this._strRef('Sheet1!' + rc2a(1, 2 + i, true, true), [serie.name]), // serie's value 297 | 'c:invertIfNegative': {'@val': '0'}, 298 | 'c:cat': this._strRef('Sheet1!' + rc2a(2, 1, true, true) + ':' + rc2a(2 + serie.labels.length - 1, 1, true, true), serie.labels), 299 | 'c:val': this._numRef('Sheet1!' + rc2a(2, 2 + i, true, true) + ':' + rc2a(2 + serie.labels.length - 1, 2 + i, true, true), serie.values, "General") 300 | } 301 | }; 302 | 303 | if (serie.color) { 304 | serieData['c:ser']['c:spPr'] = { 305 | 'a:solidFill': { 306 | 'a:srgbClr': {'@val': serie.color} 307 | } 308 | }; 309 | } 310 | else if (serie.schemeColor) { 311 | serieData['c:ser']['c:spPr'] = { 312 | 'a:solidFill': { 313 | 'a:schemeClr': {'@val': serie.schemeColor} 314 | } 315 | }; 316 | } 317 | 318 | if (serie.xml) { 319 | serieData['c:ser'] = _.merge(serieData['c:ser'], serie.xml) 320 | } 321 | 322 | 323 | // for pie charts 324 | if (serie.colors) { 325 | serieData['c:ser']['#list'] = this._colorRef(serie.colors); 326 | } 327 | 328 | return serieData; 329 | }, 330 | 331 | 332 | /// @brief returns XML snippet for a chart dataseries 333 | getSeriesRef: function (data) { 334 | return data.map(this._ser, this); 335 | }, 336 | 337 | 338 | /// @brief returns XML snippet for axis number format 339 | /// e.g. "$0" for US currency, "0%" for percentages 340 | xmlValAxisFormat: function (formatCode) { 341 | return { 342 | "c:chartSpace": { 343 | "c:chart": { 344 | "c:plotArea": { 345 | "c:valAx": { 346 | "c:majorGridlines": {}, 347 | "c:numFmt": { 348 | "@formatCode": formatCode, 349 | "@sourceLinked": "0" 350 | } 351 | } 352 | } 353 | } 354 | } 355 | } 356 | }, 357 | 358 | /// @brief returns XML snippet for an axis scale 359 | /// currently just min/max are supported 360 | _scaling: function (minValue, maxValue) { 361 | // 362 | var ref = { 363 | "c:scaling": { 364 | "c:orientation": { 365 | "@val": "minMax" 366 | } 367 | } 368 | }; 369 | if (maxValue != undefined) { 370 | ref["c:scaling"]["c:max"] = { 371 | "@val": "" + (maxValue || "") 372 | }; 373 | } 374 | if (minValue != undefined) { 375 | ref["c:scaling"]["c:max"] = { 376 | "@val": "" + (minValue || "") 377 | }; 378 | } 379 | }, 380 | 381 | _text: function(textSize) { 382 | return { 383 | "c:txPr": { 384 | "a:bodyPr": {}, 385 | "a:listStyle": {}, 386 | "a:p": { 387 | "a:pPr": { 388 | "a:defRPr": { 389 | "@sz": textSize 390 | } 391 | }, 392 | "a:endParaRPr": { 393 | "@lang": "en-US" 394 | } 395 | } 396 | } 397 | }; 398 | }, 399 | 400 | /// @brief returns XML snippet for an axis title 401 | _title: function (title) { 402 | if (typeof title == 'object') return title; // assume it's an XML representations 403 | return { 404 | "c:title": { 405 | "c:tx": { 406 | "c:rich": { 407 | "a:bodyPr": {}, 408 | "a:lstStyle": {}, 409 | "a:p": { 410 | "a:pPr": { 411 | "a:defRPr": {} 412 | }, 413 | "a:r": { 414 | "a:rPr": { 415 | "@lang": "en-US", 416 | "@dirty": "0", 417 | "@smtClean": "0" 418 | }, 419 | "a:t": title 420 | }, 421 | "a:endParaRPr": { 422 | "@lang": "en-US", 423 | "@dirty": "0" 424 | } 425 | } 426 | } 427 | }, 428 | "c:layout": {}, 429 | "c:overlay": { 430 | "@val": "0" 431 | } 432 | } 433 | } 434 | } 435 | 436 | }.initialize(chartInfo); 437 | } 438 | 439 | OfficeChart.getChartBase = function (chartInfo) { 440 | 441 | var chartBase; 442 | 443 | if (typeof chartInfo == 'string') { 444 | chartBase = _chartSpecs[chartInfo](); 445 | } 446 | else if (typeof chartInfo.renderType == 'string') { 447 | chartBase = _chartSpecs[chartInfo.renderType](chartInfo); 448 | } 449 | else if (chartInfo.xml) { 450 | chartBase = chartInfo.xml 451 | } 452 | else { 453 | throw new Error("invalid chart type") 454 | } 455 | // return deep copy so can create multiple charts from same base within one PowerPoint document 456 | return JSON.parse(JSON.stringify(chartBase)); 457 | } 458 | 459 | module.exports = OfficeChart; 460 | 461 | /*********************************************************************************************************************** 462 | // Column chart 463 | new OfficeChart({ 464 | title: 'eSurvey chart', 465 | renderType: 'column', 466 | data: [ // array of series 467 | { 468 | name: 'Income', 469 | labels: ['2005', '2006', '2007', '2008', '2009'], 470 | values: [23.5, 26.2, 30.1, 29.5, 24.6], 471 | colors: ['ff0000', '00ff00', '0000ff', 'ffff00', '00ffff'] // optional 472 | }, 473 | { 474 | name: 'Expense', 475 | labels: ['2005', '2006', '2007', '2008', '2009'], 476 | values: [18.1, 22.8, 23.9, 25.1, 25], 477 | colors: ['ff0000', '00ff00', '0000ff', 'ffff00', '00ffff'] // optional 478 | } 479 | ] 480 | }); 481 | 482 | 483 | // Pie chart 484 | new OfficeChart({ 485 | title: 'eSurvey chart', 486 | renderType: 'pie', 487 | data: [ // array of series 488 | { 489 | name: 'Income', 490 | labels: ['2005', '2006', '2007', '2008', '2009'], 491 | values: [23.5, 26.2, 30.1, 29.5, 24.6], 492 | colors: ['ff0000', '00ff00', '0000ff', 'ffff00', '00ffff'] // optional 493 | } 494 | ] 495 | }); 496 | 497 | 498 | // Clustered bar chat 499 | new OfficeChart({ 500 | title: 'eSurvey chart', 501 | renderType: 'group-bar', 502 | data: [ // array of series 503 | { 504 | name: 'Income', 505 | labels: ['2005', '2006', '2007', '2008', '2009'], 506 | values: [23.5, 26.2, 30.1, 29.5, 24.6], 507 | colors: ['ff0000', '00ff00', '0000ff', 'ffff00', '00ffff'] // optional 508 | } 509 | ] 510 | }); 511 | 512 | ***********************************************************************************************************************/ -------------------------------------------------------------------------------- /lib/pptx.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | console.json = function(obj) { console.log(JSON.stringify(obj, null,4)); } 4 | module.exports = { 5 | Presentation: require('./presentation'), 6 | Slide: require('./slide'), 7 | Shape: require('./shape'), 8 | // spPr: require('./spPr'), 9 | emu: { 10 | inch: function(val) { return Math.floor(val * 914400); }, 11 | point: function(val) { return Math.floor(val * 914400 / 72); }, 12 | px: function(val) { return Math.floor(val * 914400 / 72); }, 13 | cm: function(val) { return Math.floor(val * 360000); } 14 | } 15 | }; 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/presentation.js: -------------------------------------------------------------------------------- 1 | var JSZip = require('jszip'); // this works on browser 2 | var async = require('async'); // this works on browser 3 | var xml2js = require('xml2js'); // this works on browser? 4 | var XmlNode = require('./xmlnode'); 5 | 6 | var Slide = require('./slide'); 7 | 8 | var Presentation = function (object) { 9 | this.content = {}; 10 | }; 11 | 12 | // fundamentally asynchronous because xml2js.parseString() is async 13 | Presentation.prototype.load = function (data, done) { 14 | var zip = new JSZip(data); 15 | 16 | var content = this.content; 17 | async.each(Object.keys(zip.files), function (key, callback) { 18 | var ext = key.substr(key.lastIndexOf('.')); 19 | if (ext == '.xml' || ext == '.rels') { 20 | var xml = zip.file(key).asText(); 21 | 22 | xml2js.parseString(xml, function (err, js) { 23 | content[key] = js; 24 | callback(null); 25 | }); 26 | } 27 | else { 28 | content[key] = zip.file(key).asText(); 29 | callback(null); 30 | } 31 | }, done); 32 | }; 33 | 34 | Presentation.prototype.toJSON = function () { 35 | return this.content; 36 | }; 37 | 38 | 39 | Presentation.prototype.toBuffer = function () { 40 | var zip2 = new JSZip(); 41 | var content = this.content; 42 | for (var key in content) { 43 | if (content.hasOwnProperty(key)) { 44 | var ext = key.substr(key.lastIndexOf('.')); 45 | if (ext == '.xml' || ext == '.rels') { 46 | var builder = new xml2js.Builder({renderOpts: {pretty: false}}); 47 | var xml2 = (builder.buildObject(content[key])); 48 | zip2.file(key, xml2); 49 | } 50 | else { 51 | zip2.file(key, content[key]); 52 | } 53 | } 54 | } 55 | // zip2.file("docProps/app.xml", '172Microsoft Macintosh PowerPointOn-screen Show (4:3)123000falseTheme1Slide Titles3Office ThemeThis is the titleThis is the titleThis is the titleProven, Inc.falsefalsefalse14.0000'); 56 | zip2.file("docProps/app.xml", '00Microsoft Macintosh PowerPointOn-screen Show (4:3)02000falseTheme1Slide Titles2Office ThemePowerPoint PresentationPowerPoint PresentationProven, Inc.falsefalsefalse14.0000') 57 | var buffer = zip2.generate({type: "nodebuffer"}); 58 | return buffer; 59 | }; 60 | 61 | Presentation.prototype.registerChart = function(chartName, content) { 62 | this.content['ppt/charts/' + chartName + '.xml'] = content; 63 | 64 | // '[Content_Types].xml' .. add references to the chart and the spreadsheet 65 | this.content["[Content_Types].xml"]["Types"]["Override"].push(XmlNode() 66 | .attr('PartName', "/ppt/charts/" + chartName + ".xml") 67 | .attr('ContentType', "application/vnd.openxmlformats-officedocument.drawingml.chart+xml") 68 | .el 69 | ); 70 | 71 | var defaults = this.content["[Content_Types].xml"]["Types"]["Default"].filter(function (el) { 72 | return el['$']['Extension'] == 'xlsx' 73 | }); 74 | 75 | if (defaults.length == 0) { 76 | this.content["[Content_Types].xml"]["Types"]["Default"].push(XmlNode() 77 | .attr('Extension', 'xlsx') 78 | .attr('ContentType', "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") 79 | .el 80 | ); 81 | } 82 | } 83 | 84 | Presentation.prototype.registerChartWorkbook = function(chartName, workbookContent) { 85 | 86 | var numWorksheets = this.getWorksheetCount(); 87 | var worksheetName = 'Microsoft_Excel_Sheet' + (numWorksheets + 1) + '.xlsx'; 88 | 89 | 90 | this.content["ppt/embeddings/" + worksheetName] = workbookContent; 91 | 92 | // ppt/charts/_rels/chart1.xml.rels 93 | this.content["ppt/charts/_rels/" + chartName + ".xml.rels"] = XmlNode().setChild("Relationships", XmlNode() 94 | .attr({ 95 | 'xmlns': "http://schemas.openxmlformats.org/package/2006/relationships" 96 | }) 97 | .addChild('Relationship', XmlNode().attr({ 98 | "Id": "rId1", 99 | "Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package", 100 | "Target": "../embeddings/" + worksheetName 101 | })) 102 | ).el; 103 | } 104 | 105 | Presentation.prototype.getSlideCount = function () { 106 | return Object.keys(this.content).filter(function (key) { 107 | return key.substr(0, 16) === "ppt/slides/slide" 108 | }).length; 109 | } 110 | 111 | Presentation.prototype.getChartCount = function () { 112 | return Object.keys(this.content).filter(function (key) { 113 | return key.substr(0, 16) === "ppt/charts/chart" 114 | }).length; 115 | } 116 | 117 | Presentation.prototype.getWorksheetCount = function () { 118 | return Object.keys(this.content).filter(function (key) { 119 | return key.substr(0, 36) === "ppt/embeddings/Microsoft_Excel_Sheet" 120 | }).length; 121 | } 122 | 123 | 124 | 125 | Presentation.prototype.getSlide = function (name) { 126 | return new Slide({content: this.content['ppt/slides/' + name + '.xml'], presentation: this, name: name}); 127 | } 128 | 129 | Presentation.prototype.addSlide = function (layoutName) { 130 | var slideName = "slide" + (this.getSlideCount() + 1); 131 | 132 | var layoutKey = "ppt/slideLayouts/" + layoutName + ".xml"; 133 | var slideKey = "ppt/slides/" + slideName + ".xml"; 134 | var relsKey = "ppt/slides/_rels/" + slideName + ".xml.rels"; 135 | 136 | 137 | // create slide 138 | // var slideContent = this.content[layoutKey]["p:sldLayout"]; 139 | 140 | 141 | 142 | //var sld = this.content["ppt/slides/slide1.xml"]; // this is cheating, copying an existing slide 143 | var sld = this.content[layoutKey]["p:sldLayout"]; 144 | delete sld['$']["preserve"]; 145 | delete sld['$']["type"]; 146 | 147 | var slideContent = { 148 | "p:sld" : sld 149 | }; 150 | 151 | 152 | slideContent = JSON.parse(JSON.stringify(slideContent)); 153 | 154 | this.content[slideKey] = slideContent; //{ "p:sld": slideContent}; 155 | 156 | 157 | var slide = new Slide({content: slideContent, presentation: this, name: slideName}); 158 | 159 | // add to [Content Types].xml 160 | this.content["[Content_Types].xml"]["Types"]["Override"].push({ 161 | "$": { 162 | "PartName": "/ppt/slides/" + slideName + ".xml", 163 | "ContentType": "application/vnd.openxmlformats-officedocument.presentationml.slide+xml" 164 | } 165 | }); 166 | 167 | //add it rels to slidelayout 168 | this.content[relsKey] = { 169 | "Relationships": { 170 | "$": { 171 | "xmlns": "http://schemas.openxmlformats.org/package/2006/relationships" 172 | }, 173 | "Relationship": [ 174 | { 175 | "$": { 176 | "Id": "rId1", 177 | "Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout", 178 | "Target": "../slideLayouts/" + layoutName + ".xml" 179 | } 180 | } 181 | ] 182 | } 183 | }; 184 | 185 | // add it to ppt/_rels/presentation.xml.rels 186 | var rId = "rId" + (this.content["ppt/_rels/presentation.xml.rels"]["Relationships"]["Relationship"].length + 1); 187 | this.content["ppt/_rels/presentation.xml.rels"]["Relationships"]["Relationship"].push({ 188 | "$": { 189 | "Id": rId, 190 | "Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide", 191 | "Target": "slides/" + slideName + ".xml" 192 | } 193 | }); 194 | 195 | // add it to ppt/presentation.xml 196 | var maxId = 0; 197 | this.content["ppt/presentation.xml"]["p:presentation"]["p:sldIdLst"][0]["p:sldId"].forEach(function (ob) { 198 | if (+ob["$"]["id"] > maxId) maxId = +ob["$"]["id"] 199 | }) 200 | this.content["ppt/presentation.xml"]["p:presentation"]["p:sldIdLst"][0]["p:sldId"].push({ 201 | "$": { 202 | "id": "" + (+maxId + 1), 203 | "r:id": rId 204 | } 205 | }); 206 | 207 | // increment slidecount 208 | var sldCount = this.getSlideCount(); 209 | this.content["docProps/app.xml"]["Properties"]["Slides"][0] = sldCount ; 210 | 211 | 212 | return slide; 213 | } 214 | 215 | module.exports = Presentation; -------------------------------------------------------------------------------- /lib/shape.js: -------------------------------------------------------------------------------- 1 | var XmlNode = require('./xmlnode'); 2 | 3 | var shapeProperties = require('./shapeproperties'); 4 | var defaults = require('./defaults'); 5 | var clone = require('./util/clone') 6 | 7 | //====================================================================================================================== 8 | // Shape (p:sp) 9 | //====================================================================================================================== 10 | 11 | var Shape = function (content) { 12 | this.content = content || clone(defaults["p:sp"]); 13 | } 14 | 15 | Shape.prototype.text = function (text) { 16 | if (text) { 17 | this.content['p:txBody'][0]['a:p'][0]['a:r'][0]['a:t'][0] = text; 18 | return this; 19 | } 20 | else { 21 | return this.content['p:txBody'][0]['a:p'][0]['a:r'][0]['a:t'][0]; 22 | } 23 | } 24 | 25 | Shape.prototype.shapeProperties = function () { 26 | return new shapeProperties(this.content["p:spPr"][0]) 27 | } 28 | 29 | module.exports = Shape; -------------------------------------------------------------------------------- /lib/shapeProperties.js: -------------------------------------------------------------------------------- 1 | var XmlNode = require('./xmlnode'); 2 | 3 | 4 | //====================================================================================================================== 5 | // spPr ShapeProperties 6 | //====================================================================================================================== 7 | 8 | var shapeProperties = function (content) { 9 | this.content = content || this.getDefault(); 10 | } 11 | 12 | 13 | shapeProperties.prototype.getDefault = function () { 14 | 15 | return XmlNode() 16 | .addChild("a:xfrm", XmlNode() 17 | .addChild("a:off", XmlNode().attr({ 18 | "x": "6578600", 19 | "y": "787400" 20 | })) 21 | .addChild("a:ext", XmlNode().attr({ 22 | "cx": "1181100", 23 | "cy": "1181100" 24 | }) 25 | ) 26 | 27 | ) 28 | .addChld("a:prstGeom", XmlNode().attr({'prst': 'ellipse'}).addChild('a:avList', XmlNode())) 29 | ; 30 | 31 | 32 | return { 33 | "a:xfrm": [ 34 | { 35 | "a:off": [ 36 | { 37 | "$": { 38 | "x": "6578600", 39 | "y": "787400" 40 | } 41 | } 42 | ], 43 | "a:ext": [ 44 | { 45 | "$": { 46 | "cx": "1181100", 47 | "cy": "1181100" 48 | } 49 | } 50 | ] 51 | } 52 | ], 53 | "a:prstGeom": [ 54 | { 55 | "$": { 56 | "prst": "ellipse" 57 | }, 58 | "a:avLst": [ 59 | "" 60 | ] 61 | } 62 | ] 63 | }; 64 | } 65 | 66 | shapeProperties.prototype.toJSON = function () { 67 | return { 68 | x: this.x(), 69 | y: this.y(), 70 | cx: this.cx(), 71 | cy: this.cy(), 72 | prstGeom: this.prstGeom() 73 | } 74 | } 75 | 76 | shapeProperties.prototype.x = function (val) { 77 | if (arguments.length == 0) return this.content["a:xfrm"][0]["a:off"][0]['$'].x; 78 | else this.content["a:xfrm"][0]["a:off"][0]['$'].x = val; 79 | return this; 80 | } 81 | shapeProperties.prototype.y = function (val) { 82 | if (arguments.length == 0) return this.content["a:xfrm"][0]["a:off"][0]['$'].y; 83 | else this.content["a:xfrm"][0]["a:off"][0]['$'].y = val; 84 | return this; 85 | } 86 | shapeProperties.prototype.cx = function (val) { 87 | if (arguments.length == 0) return this.content["a:xfrm"][0]["a:ext"][0]['$'].cx; 88 | else this.content["a:xfrm"][0]["a:ext"][0]['$'].cx = val; 89 | return this; 90 | } 91 | shapeProperties.prototype.cy = function (val) { 92 | if (arguments.length == 0) return this.content["a:xfrm"][0]["a:ext"][0]['$'].cy; 93 | else this.content["a:xfrm"][0]["a:ext"][0]['$'].cy = val; 94 | return this; 95 | } 96 | 97 | // see http://www.officeopenxml.com/drwSp-prstGeom.php 98 | shapeProperties.prototype.prstGeom = function (shape) { 99 | if (arguments.length == 0) return this.content["a:prstGeom"][0]["$"]["prst"]; 100 | else this.content["a:prstGeom"][0]["$"]["prst"] = shape; 101 | return this; 102 | } 103 | 104 | module.exports = shapeProperties; 105 | 106 | //https://msdn.microsoft.com/en-us/library/documentformat.openxml.drawing.presetgeometry(v=office.14).aspx -------------------------------------------------------------------------------- /lib/slide.js: -------------------------------------------------------------------------------- 1 | var Shape = require('./shape'); 2 | var Chart = require('./chart') 3 | 4 | 5 | //====================================================================================================================== 6 | // Slide 7 | //====================================================================================================================== 8 | 9 | var Slide = function (args) { 10 | this.content = args.content; 11 | this.presentation = args.presentation; 12 | this.name = args.name; 13 | 14 | // TODO: Validate arguments 15 | }; 16 | 17 | Slide.prototype.getShapes = function () { 18 | 19 | // TODO break out getShapeTree 20 | return this.content["p:sld"]["p:cSld"][0]["p:spTree"][0]['p:sp'].map(function (sp) { 21 | return new Shape(sp); 22 | }); 23 | }; 24 | 25 | Slide.prototype.addChart = function (data, done) { 26 | var self = this; 27 | var chartName = "chart"+(this.presentation.getChartCount() + 1); 28 | var chart = new Chart({slide: this, presentation: this.presentation, name: chartName}); 29 | var slideName = this.name; 30 | 31 | chart.load(data, function(err, data) { // TODO pass it real data 32 | self.content["p:sld"]["p:cSld"][0]["p:spTree"][0]["p:graphicFrame"] = chart.content; //jsChartFrame["p:graphicFrame"]; 33 | 34 | // Add entry to slide1.xml.rels 35 | // There should a slide-level and/or presentation-level method to add/track rels 36 | var rels = self.presentation.content['ppt/slides/_rels/' + slideName + '.xml.rels']; 37 | var numRels = rels["Relationships"]["Relationship"].length; 38 | var rId = "rId"+(numRels + 1); 39 | var numRels = rels["Relationships"]["Relationship"].push({ 40 | "$": { 41 | "Id": rId , 42 | "Type": "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart", 43 | "Target": "../charts/" + chartName + ".xml" 44 | } 45 | }); 46 | done(null, self); 47 | }); 48 | }; 49 | 50 | Slide.prototype.addShape = function () { 51 | var shape = new Shape(); 52 | this.content["p:sld"]["p:cSld"][0]["p:spTree"][0]['p:sp'].push(shape.content); 53 | return shape; 54 | }; 55 | 56 | module.exports = Slide; -------------------------------------------------------------------------------- /lib/util/clone.js: -------------------------------------------------------------------------------- 1 | module.exports = function clone(obj) { return JSON.parse(JSON.stringify(obj))} -------------------------------------------------------------------------------- /lib/xmlnode.js: -------------------------------------------------------------------------------- 1 | // Super light implementation of an XML DOM 2 | // Right now this keeps only the right hand side, not the left, so it doesn't keep its own tag 3 | // The tag is defined implicitly by its hash key in th 4 | 5 | 6 | 7 | var XmlNode = function (options) { 8 | 9 | if (this instanceof XmlNode) { 10 | this.el = {}; 11 | if (typeof options == 'string') this.tagName = options; 12 | else if (options) this.tagName = options.tagName; 13 | } 14 | else return new XmlNode(options); 15 | } 16 | 17 | module.exports = XmlNode; 18 | 19 | XmlNode.prototype.toJSON = function () { 20 | return this.el; 21 | } 22 | 23 | // pass in a single key, value pair, e.g. XmlNode().attr('color', 'red') to set one attribute 24 | // or pass in an associative array, e.g. XmlNode().attr({color: 'red') to set multiple attributes 25 | // or pass in just a key to get the attribute value 26 | XmlNode.prototype.attr = function (key, val) { 27 | if (typeof val == 'number') val = ''+val; 28 | if (typeof key === 'undefined') { 29 | // return all attributes, if any are defined 30 | return this.el['$']; 31 | } 32 | else if (arguments.length == 1 && (typeof key == 'object')) { 33 | // assign attributes 34 | this.el['$'] = this.el['$'] || {}; 35 | for (var attrName in key) { 36 | if (key.hasOwnProperty(attrName)) { 37 | this.el['$'][attrName] = key[attrName]; 38 | } 39 | } 40 | return this; 41 | } 42 | else if (typeof val === 'undefined') { 43 | // get the attribute value 44 | return this.el['$'] ? this.el['$'][key] : undefined; 45 | 46 | } 47 | else { 48 | this.el['$'] = this.el['$'] || {}; 49 | this.el['$'][key] = val; 50 | return this; 51 | } 52 | } 53 | 54 | XmlNode.prototype.addChild = function (tag, node) { 55 | // if (typeof node == 'string') this.el[tag] = node; 56 | // else { 57 | this.el[tag] = this.el[tag] || []; 58 | this.el[tag].push((node instanceof XmlNode) ? node.el : node); 59 | // } 60 | return this; 61 | 62 | } 63 | 64 | 65 | XmlNode.prototype.setChild = function (tag, node) { 66 | this.el[tag] = (node instanceof XmlNode) ? node.el : node; 67 | return this; 68 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-pptx", 3 | "version": "0.1.1", 4 | "description": "Pure Javascript reader/writer for PowerPoint .pptx files", 5 | "main": "lib/pptx.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/protobi/js-pptx.git" 12 | }, 13 | "author": "Pieter Sheth-Voss", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/protobi/js-pptx/issues" 17 | }, 18 | "homepage": "https://github.com/protobi/js-pptx", 19 | "dependencies": { 20 | "async": "^1.4.0", 21 | "jszip": "^2.5.0", 22 | "lodash": "^3.10.0", 23 | "msexcel-builder": "0.0.2", 24 | "query": "git://github.com/protobi/query.git", 25 | "xml2js": "^0.4.9", 26 | "xmlbuilder": "^2.6.4" 27 | }, 28 | "devDependencies": {}, 29 | "directories": { 30 | "test": "test" 31 | }, 32 | "keywords": [ 33 | "PowerPoint", 34 | "parser/generator" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /test/chart.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require('assert'); 4 | var fs = require("fs"); 5 | var PPTX = require('..'); 6 | var xml2js = require('xml2js'); 7 | var xmlbuilder = require('xmlbuilder') 8 | 9 | var INFILE = './lab/chart-null/chart-null.pptx'; 10 | var OUTFILE = '/tmp/chart.pptx'; 11 | 12 | describe('PPTX', function () { 13 | 14 | it('can read, modify, write and read', function (done) { 15 | fs.readFile(INFILE, function (err, data) { 16 | if (err) throw err; 17 | var pptx = new PPTX.Presentation(); 18 | 19 | 20 | pptx.load(data, function (err) { 21 | 22 | var slide1 = pptx.getSlide('slide1'); 23 | var slide2 = pptx.addSlide("slideLayout6"); 24 | slide1.addChart(barChart, function (err, chart) { 25 | if (err) throw(err); 26 | 27 | slide2.addChart(barChart2, function (err, chart) { 28 | if (err) throw(err); 29 | 30 | fs.writeFile(OUTFILE, pptx.toBuffer(), function (err) { 31 | if (err) throw err; 32 | console.log("open " + OUTFILE) 33 | done(); 34 | }); 35 | }); 36 | }); 37 | }); 38 | }); 39 | }); 40 | }); 41 | 42 | // 43 | //Series 1 Series 2 Series 3 44 | //Category 1 4.3 2.4 2 45 | //Category 2 2.5 4.4 2 46 | //Category 3 3.5 1.8 3 47 | //Category 4 4.5 2.8 5 48 | 49 | var barChart = { 50 | title: 'Sample bar chart', 51 | renderType: 'bar', 52 | data: [ 53 | { 54 | name: 'Series 1', 55 | labels: ['Category 1', 'Category 2', 'Category 3', 'Category 4'], 56 | values: [4.3, 2.5, 3.5, 4.5] 57 | }, 58 | { 59 | name: 'Series 2', 60 | labels: ['Category 1', 'Category 2', 'Category 3', 'Category 4'], 61 | values: [2.4, 4.4, 1.8, 2.8] 62 | }, 63 | { 64 | name: 'Series 3', 65 | labels: ['Category 1', 'Category 2', 'Category 3', 'Category 4'], 66 | values: [2.0, 2.0, 3.0, 5.0] 67 | } 68 | ] 69 | } 70 | 71 | var barChart2 = { 72 | title: 'Sample bar chart', 73 | renderType: 'bar', 74 | xmlOptions: { 75 | "c:title": { 76 | "c:tx": { 77 | "c:rich": { 78 | "a:p": { 79 | "a:r": { 80 | "a:t": "Override title via XML" 81 | } 82 | } 83 | } 84 | } 85 | } 86 | }, 87 | data: [ 88 | { 89 | name: 'europe', 90 | labels: ['Y2003', 'Y2004', 'Y2005'], 91 | values: [2.5, 2.6, 2.8], 92 | color: 'ff0000' 93 | }, 94 | { 95 | name: 'namerica', 96 | labels: ['Y2003', 'Y2004', 'Y2005'], 97 | values: [2.5, 2.7, 2.9], 98 | color: '00ff00' 99 | }, 100 | { 101 | name: 'asia', 102 | labels: ['Y2003', 'Y2004', 'Y2005'], 103 | values: [2.1, 2.2, 2.4], 104 | color: '0000ff' 105 | }, 106 | { 107 | name: 'lamerica', 108 | labels: ['Y2003', 'Y2004', 'Y2005'], 109 | values: [0.3, 0.3, 0.3], 110 | color: 'ffff00' 111 | }, 112 | { 113 | name: 'meast', 114 | labels: ['Y2003', 'Y2004', 'Y2005'], 115 | values: [0.2, 0.3, 0.3], 116 | color: 'ff00ff' 117 | }, 118 | { 119 | name: 'africa', 120 | labels: ['Y2003', 'Y2004', 'Y2005'], 121 | values: [0.1, 0.1, 0.1], 122 | color: '00ffff' 123 | } 124 | 125 | ] 126 | }; -------------------------------------------------------------------------------- /test/example.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require('assert'); 4 | var fs = require("fs"); 5 | var PPTX = require('..'); 6 | var xml2js = require('xml2js'); 7 | var xmlbuilder = require('xmlbuilder') 8 | 9 | 10 | var INFILE = './test/files/parts3.pptx'; 11 | var OUTFILE = './test/files/parts3-a.pptx'; 12 | 13 | describe('PPTX', function () { 14 | 15 | it('can read, modify, write and read', function (done) { 16 | fs.readFile(INFILE, function (err, data) { 17 | if (err) throw err; 18 | var pptx = new PPTX.Presentation(); 19 | pptx.load(data, function (err) { 20 | 21 | var slide3 = pptx.addSlide("slideLayout3"); 22 | var slide4 = pptx.addSlide("slideLayout2"); 23 | 24 | var slide1 = pptx.getSlide('slide1'); 25 | var shapes = slide1.getShapes(); 26 | 27 | shapes[3] 28 | .text("Now it's a trapezoid") 29 | .shapeProperties() 30 | .x(PPTX.emu.inch(1)) 31 | .y(PPTX.emu.inch(1)) 32 | .cx(PPTX.emu.inch(2)) 33 | .cy(PPTX.emu.inch(0.75)) 34 | .prstGeom('trapezoid'); 35 | 36 | var triangle = slide1.addShape() 37 | .text("Triangle") 38 | .shapeProperties() 39 | .x(PPTX.emu.inch(2)) 40 | .y(PPTX.emu.inch(2)) 41 | .cx(PPTX.emu.inch(2)) 42 | .cy(PPTX.emu.inch(2)) 43 | .prstGeom('triangle'); 44 | 45 | for (var i= 0; i<20; i++) { 46 | slide3.addShape() 47 | .text(""+i) 48 | .shapeProperties() 49 | .x(PPTX.emu.inch((Math.random()*10))) 50 | .y(PPTX.emu.inch((Math.random()*6))) 51 | .cx(PPTX.emu.inch(1)) 52 | .cy(PPTX.emu.inch(1)) 53 | .prstGeom('ellipse'); 54 | } 55 | 56 | var chart = slide1.addChart(barChart, function (err, chart) { 57 | 58 | 59 | fs.writeFile(OUTFILE, pptx.toBuffer(), function (err) { 60 | if (err) throw err; 61 | 62 | fs.readFile(OUTFILE, function (err, data) { 63 | var check = new PPTX.Presentation(); 64 | check.load(data, function (err) { 65 | 66 | var props = check.getSlide('slide1').getShapes()[3].shapeProperties().toJSON(); 67 | assert.deepEqual(props, { x: '914400', 68 | y: '914400', 69 | cx: '1828800', 70 | cy: '685800', 71 | prstGeom: 'trapezoid' }); 72 | }); 73 | console.log("open "+OUTFILE) 74 | done(); 75 | }); 76 | }); 77 | }); 78 | 79 | }); 80 | }); 81 | }); 82 | }); 83 | 84 | var barChart = { 85 | title: 'Sample bar chart', 86 | renderType: 'bar', 87 | data: [ 88 | { 89 | name: 'Series 1', 90 | labels: ['Category 1', 'Category 2', 'Category 3', 'Category 4'], 91 | values: [4.3, 2.5, 3.5, 4.5] 92 | }, 93 | { 94 | name: 'Series 2', 95 | labels: ['Category 1', 'Category 2', 'Category 3', 'Category 4'], 96 | values: [2.4, 4.4, 1.8, 2.8] 97 | }, 98 | { 99 | name: 'Series 3', 100 | labels: ['Category 1', 'Category 2', 'Category 3', 'Category 4'], 101 | values: [2.0, 2.0, 3.0, 5.0] 102 | } 103 | ] 104 | } -------------------------------------------------------------------------------- /test/files/minimal.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/won21kr/js-pptx/7aed5fb3cc88502a58993603936d71eea603bbfc/test/files/minimal.pptx -------------------------------------------------------------------------------- /test/files/parts3-a.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/won21kr/js-pptx/7aed5fb3cc88502a58993603936d71eea603bbfc/test/files/parts3-a.pptx -------------------------------------------------------------------------------- /test/files/parts3-b.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/won21kr/js-pptx/7aed5fb3cc88502a58993603936d71eea603bbfc/test/files/parts3-b.pptx -------------------------------------------------------------------------------- /test/files/parts3.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/won21kr/js-pptx/7aed5fb3cc88502a58993603936d71eea603bbfc/test/files/parts3.pptx -------------------------------------------------------------------------------- /test/synthesize.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require('assert'); 4 | var fs = require("fs"); 5 | var JSZip = require('jszip') 6 | 7 | var INFILE = './test/files/minimal.pptx'; 8 | var STORE = './test/files/minimal.json'; 9 | var OUTFILE = './test/files/minimal-copy.pptx'; 10 | 11 | var zip1 = new JSZip(fs.readFileSync(INFILE)); 12 | var copy = {}; 13 | 14 | Object.keys(zip1.files).forEach(function (key) { 15 | copy[key] = zip1.file(key).asText(); 16 | }); 17 | 18 | fs.writeFileSync(STORE, JSON.stringify(copy, null,4)) 19 | 20 | var json = fs.readFileSync(STORE, 'utf8'); 21 | var obj = JSON.parse(json); 22 | 23 | var zip2 = new JSZip(); 24 | for (var key in obj) { 25 | zip2.file(key, obj[key]); 26 | } 27 | 28 | var buffer = zip2.generate({type:"nodebuffer", compression: 'DEFLATE'}); 29 | 30 | fs.writeFile(OUTFILE, buffer, function(err) { 31 | if (err) throw err; 32 | }); 33 | 34 | var zip3 = new JSZip(); 35 | zip3.file('json', JSON.stringify(copy, null,4)) 36 | var buffer3 = zip3.generate({type:"nodebuffer", compression: 'DEFLATE'}); 37 | 38 | fs.writeFile('./test/files/minimal.json.jar', buffer3, function(err) { 39 | if (err) throw err; 40 | }); -------------------------------------------------------------------------------- /test/xmlnode.js: -------------------------------------------------------------------------------- 1 | var XmlNode = require('../lib/xmlnode') 2 | var assert = require('assert'); 3 | 4 | describe("XmlNode", function () { 5 | it('constructs with new', function () { 6 | var node = new XmlNode(); 7 | node.attr('color', '#39C'); 8 | assert.equal(node.attr('color'), '#39C'); 9 | assert.deepEqual(node.el, { $: { color: "#39C"}}) 10 | }); 11 | 12 | it('constructs as function', function () { 13 | var node = XmlNode().attr('color', '#39C') 14 | assert.equal(node.attr('color'), '#39C'); 15 | assert.deepEqual(node.el, { $: { color: "#39C"}}) 16 | }); 17 | 18 | it('adds a child', function () { 19 | var node = XmlNode().attr("color", "#39C").addChild("p:sld", XmlNode().attr("color", "#9B6")); 20 | assert.deepEqual(node.el, { $: { color: "#39C"}, "p:sld": [ 21 | { $: { color: "#9B6"}} 22 | ]}) 23 | 24 | }); 25 | it('sets a child', function () { 26 | var node = XmlNode().attr("color", "#39C").setChild("p:sld", XmlNode().attr("color", "#9B6")); 27 | assert.deepEqual(node.el, { $: { color: "#39C"}, "p:sld": { $: { color: "#9B6"}}}) 28 | 29 | }); 30 | // it('takes an element in its constructor', function () { 31 | // var node = XmlNode({ $: { color: "#39C"}, "p:sld": { $: { color: "#9B6"}}}); 32 | // assert.deepEqual(node.el, { $: { color: "#39C"}, "p:sld": { $: { color: "#9B6"}}}) 33 | // }); 34 | // it('exposes toJSON() as a public method', function () { 35 | // var node = XmlNode({ $: { color: "#39C"}, "p:sld": { $: { color: "#9B6"}}}); 36 | // assert.deepEqual(node.toJSON(), { $: { color: "#39C"}, "p:sld": { $: { color: "#9B6"}}}) 37 | // }); 38 | it('generates a spPr object', function () { 39 | var node = XmlNode() 40 | .addChild("a:xfrm", XmlNode() 41 | .addChild("a:off", XmlNode().attr({ 42 | "x": "6578600", 43 | "y": "787400" 44 | })) 45 | .addChild("a:ext", XmlNode().attr({ 46 | "cx": "1181100", 47 | "cy": "1181100" 48 | }) 49 | ) 50 | 51 | ) 52 | .addChild("a:prstGeom", XmlNode().attr({'prst': 'ellipse'}).addChild('a:avLst', XmlNode())) 53 | ; 54 | 55 | var expected = { 56 | "a:xfrm": [ 57 | { 58 | "a:off": [ 59 | { 60 | "$": { 61 | "x": "6578600", 62 | "y": "787400" 63 | } 64 | } 65 | ], 66 | "a:ext": [ 67 | { 68 | "$": { 69 | "cx": "1181100", 70 | "cy": "1181100" 71 | } 72 | } 73 | ] 74 | } 75 | ], 76 | "a:prstGeom": [ 77 | { 78 | "$": { 79 | "prst": "ellipse" 80 | }, 81 | "a:avLst": [ 82 | {} 83 | ] 84 | } 85 | ] 86 | } 87 | 88 | assert.deepEqual(node.toJSON(), expected) 89 | }) 90 | }); -------------------------------------------------------------------------------- /xml2js.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var xml2js = require('xml2js') 3 | var arguments = process.argv.slice(2); 4 | var INFILE = arguments[0]; 5 | var OUTFILE = arguments[1]; 6 | 7 | fs.readFile(INFILE, 'utf8', function(err, xml) { 8 | if (err) throw(err); 9 | xml2js.parseString(xml, function(err, js) { 10 | if (err) throw(err); 11 | if (OUTFILE) { 12 | var txt = "module.exports = " + JSON.stringify(js,null,4); 13 | fs.writeFile(OUTFILE, txt, 'utf8', function(err) { 14 | if (err) throw(err); 15 | console.log("File written to "+OUTFILE); 16 | } ) 17 | } 18 | else console.log(JSON.stringify(js,null,4)); 19 | }) 20 | 21 | }); 22 | --------------------------------------------------------------------------------