a .NET library that can read/write Office formats without Microsoft Office installed. No COM+, no interop.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

765 lines
21 KiB

  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for Additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. using System;
  16. using System.IO;
  17. using NPOI.OpenXml4Net.Exceptions;
  18. using NPOI.OpenXml4Net.OPC;
  19. using NPOI.OpenXml4Net.OPC.Internal;
  20. using NPOI.OpenXmlFormats;
  21. namespace NPOI
  22. {
  23. /**
  24. * The core document properties
  25. */
  26. public class CoreProperties
  27. {
  28. private PackagePropertiesPart part;
  29. internal CoreProperties(PackagePropertiesPart part)
  30. {
  31. this.part = part;
  32. }
  33. public String Category
  34. {
  35. get
  36. {
  37. return part.GetCategoryProperty();
  38. }
  39. set
  40. {
  41. part.SetCategoryProperty(value);
  42. }
  43. }
  44. public String ContentStatus
  45. {
  46. get
  47. {
  48. return part.GetContentStatusProperty();
  49. }
  50. set
  51. {
  52. part.SetContentStatusProperty(value);
  53. }
  54. }
  55. public String ContentType
  56. {
  57. get
  58. {
  59. return part.GetContentTypeProperty();
  60. }
  61. set
  62. {
  63. part.SetContentTypeProperty(value);
  64. }
  65. }
  66. public DateTime? Created
  67. {
  68. get
  69. {
  70. return part.GetCreatedProperty();
  71. }
  72. set
  73. {
  74. part.SetCreatedProperty(value);
  75. }
  76. }
  77. public void SetCreated(String date)
  78. {
  79. part.SetCreatedProperty(date);
  80. }
  81. public String Creator
  82. {
  83. get
  84. {
  85. return part.GetCreatorProperty();
  86. }
  87. set
  88. {
  89. part.SetCreatorProperty(value);
  90. }
  91. }
  92. public String Description
  93. {
  94. get
  95. {
  96. return part.GetDescriptionProperty();
  97. }
  98. set
  99. {
  100. part.SetDescriptionProperty(value);
  101. }
  102. }
  103. public String Identifier
  104. {
  105. get
  106. {
  107. return part.GetIdentifierProperty();
  108. }
  109. set
  110. {
  111. part.SetIdentifierProperty(value);
  112. }
  113. }
  114. public String Keywords
  115. {
  116. get
  117. {
  118. return part.GetKeywordsProperty();
  119. }
  120. set
  121. {
  122. part.SetKeywordsProperty(value);
  123. }
  124. }
  125. public DateTime? LastPrinted
  126. {
  127. get
  128. {
  129. return part.GetLastPrintedProperty();
  130. }
  131. set
  132. {
  133. part.SetLastPrintedProperty(value);
  134. }
  135. }
  136. public void SetLastPrinted(String date)
  137. {
  138. part.SetLastPrintedProperty(date);
  139. }
  140. public String LastModifiedByUser
  141. {
  142. get
  143. {
  144. return part.GetLastModifiedByProperty();
  145. }
  146. set
  147. {
  148. part.SetLastModifiedByProperty(value);
  149. }
  150. }
  151. public DateTime? Modified
  152. {
  153. get
  154. {
  155. return part.GetModifiedProperty();
  156. }
  157. set
  158. {
  159. part.SetModifiedProperty(value);
  160. }
  161. }
  162. public void SetModified(String date)
  163. {
  164. part.SetModifiedProperty(date);
  165. }
  166. public String Subject
  167. {
  168. get
  169. {
  170. return part.GetSubjectProperty();
  171. }
  172. set
  173. {
  174. part.SetSubjectProperty(value);
  175. }
  176. }
  177. public String Title
  178. {
  179. get
  180. {
  181. return part.GetTitleProperty();
  182. }
  183. set
  184. {
  185. part.SetTitleProperty(value);
  186. }
  187. }
  188. public String Revision
  189. {
  190. get
  191. {
  192. return part.GetRevisionProperty();
  193. }
  194. set
  195. {
  196. try
  197. {
  198. long.Parse(value);
  199. part.SetRevisionProperty(value);
  200. }
  201. catch (FormatException) { }
  202. }
  203. }
  204. public PackagePropertiesPart GetUnderlyingProperties()
  205. {
  206. return part;
  207. }
  208. }
  209. /**
  210. * Extended document properties
  211. */
  212. public class ExtendedProperties
  213. {
  214. public ExtendedPropertiesDocument props;
  215. internal ExtendedProperties(ExtendedPropertiesDocument props)
  216. {
  217. this.props = props;
  218. }
  219. public CT_ExtendedProperties GetUnderlyingProperties()
  220. {
  221. return props.GetProperties();
  222. }
  223. public String Template
  224. {
  225. get
  226. {
  227. return props.GetProperties().Template;
  228. }
  229. }
  230. public String Manager
  231. {
  232. get { return props.GetProperties().Manager; }
  233. }
  234. public String Company
  235. {
  236. get { return props.GetProperties().Company; }
  237. }
  238. public String PresentationFormat
  239. {
  240. get { return props.GetProperties().PresentationFormat; }
  241. }
  242. public String Application
  243. {
  244. get { return props.GetProperties().Application; }
  245. }
  246. public String AppVersion
  247. {
  248. get { return props.GetProperties().AppVersion; }
  249. }
  250. public int Pages
  251. {
  252. get
  253. {
  254. if (props.GetProperties().IsSetPages())
  255. {
  256. return props.GetProperties().Pages;
  257. }
  258. return -1;
  259. }
  260. }
  261. public int Words
  262. {
  263. get
  264. {
  265. if (props.GetProperties().IsSetWords())
  266. {
  267. return props.GetProperties().Words;
  268. }
  269. return -1;
  270. }
  271. }
  272. public int Characters
  273. {
  274. get
  275. {
  276. if (props.GetProperties().IsSetCharacters())
  277. {
  278. return props.GetProperties().Characters;
  279. }
  280. return -1;
  281. }
  282. }
  283. public int CharactersWithSpaces
  284. {
  285. get
  286. {
  287. if (props.GetProperties().IsSetCharactersWithSpaces())
  288. {
  289. return props.GetProperties().CharactersWithSpaces;
  290. }
  291. return -1;
  292. }
  293. }
  294. public int Lines
  295. {
  296. get
  297. {
  298. if (props.GetProperties().IsSetLines())
  299. {
  300. return props.GetProperties().Lines;
  301. }
  302. return -1;
  303. }
  304. }
  305. public int Paragraphs
  306. {
  307. get
  308. {
  309. if (props.GetProperties().IsSetParagraphs())
  310. {
  311. return props.GetProperties().Paragraphs;
  312. }
  313. return -1;
  314. }
  315. }
  316. public int Slides
  317. {
  318. get
  319. {
  320. if (props.GetProperties().IsSetSlides())
  321. {
  322. return props.GetProperties().Slides;
  323. }
  324. return -1;
  325. }
  326. }
  327. public int Notes
  328. {
  329. get
  330. {
  331. if (props.GetProperties().IsSetNotes())
  332. {
  333. return props.GetProperties().Notes;
  334. }
  335. return -1;
  336. }
  337. }
  338. public int TotalTime
  339. {
  340. get
  341. {
  342. if (props.GetProperties().IsSetTotalTime())
  343. {
  344. return props.GetProperties().TotalTime;
  345. }
  346. return -1;
  347. }
  348. }
  349. public int HiddenSlides
  350. {
  351. get
  352. {
  353. if (props.GetProperties().IsSetHiddenSlides())
  354. {
  355. return props.GetProperties().HiddenSlides;
  356. }
  357. return -1;
  358. }
  359. }
  360. public int MMClips
  361. {
  362. get
  363. {
  364. if (props.GetProperties().IsSetMMClips())
  365. {
  366. return props.GetProperties().MMClips;
  367. }
  368. return -1;
  369. }
  370. }
  371. public String HyperlinkBase
  372. {
  373. get { return props.GetProperties().HyperlinkBase; }
  374. }
  375. }
  376. /**
  377. * Custom document properties
  378. */
  379. public class CustomProperties
  380. {
  381. /**
  382. * Each custom property element Contains an fmtid attribute
  383. * with the same GUID value ({D5CDD505-2E9C-101B-9397-08002B2CF9AE}).
  384. */
  385. public static String FORMAT_ID = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
  386. public CustomPropertiesDocument props;
  387. internal CustomProperties(CustomPropertiesDocument props)
  388. {
  389. this.props = props;
  390. }
  391. public CT_CustomProperties GetUnderlyingProperties()
  392. {
  393. return props.GetProperties();
  394. }
  395. /**
  396. * Add a new property
  397. *
  398. * @param name the property name
  399. * @throws IllegalArgumentException if a property with this name already exists
  400. */
  401. private CT_Property Add(String name)
  402. {
  403. if (Contains(name))
  404. {
  405. throw new ArgumentException("A property with this name " +
  406. "already exists in the custom properties");
  407. }
  408. CT_Property p = props.GetProperties().AddNewProperty();
  409. int pid = NextPid();
  410. p.pid = pid;
  411. p.fmtid = FORMAT_ID;
  412. p.name = name;
  413. return p;
  414. }
  415. /**
  416. * Add a new string property
  417. *
  418. * @throws IllegalArgumentException if a property with this name already exists
  419. */
  420. public void AddProperty(String name, String value)
  421. {
  422. CT_Property p = Add(name);
  423. p.ItemElementName = ItemChoiceType.lpwstr;
  424. p.Item = value;
  425. }
  426. /**
  427. * Add a new double property
  428. *
  429. * @throws IllegalArgumentException if a property with this name already exists
  430. */
  431. public void AddProperty(String name, double value)
  432. {
  433. CT_Property p = Add(name);
  434. p.ItemElementName = ItemChoiceType.r8;
  435. p.Item = value;
  436. }
  437. /**
  438. * Add a new integer property
  439. *
  440. * @throws IllegalArgumentException if a property with this name already exists
  441. */
  442. public void AddProperty(String name, int value)
  443. {
  444. CT_Property p = Add(name);
  445. p.ItemElementName = ItemChoiceType.i4;
  446. p.Item = value;
  447. }
  448. /**
  449. * Add a new bool property
  450. *
  451. * @throws IllegalArgumentException if a property with this name already exists
  452. */
  453. public void AddProperty(String name, bool value)
  454. {
  455. CT_Property p = Add(name);
  456. p.ItemElementName = ItemChoiceType.@bool;
  457. p.Item = value;
  458. }
  459. /**
  460. * Generate next id that uniquely relates a custom property
  461. *
  462. * @return next property id starting with 2
  463. */
  464. protected int NextPid()
  465. {
  466. int propid = 1;
  467. foreach (CT_Property p in props.GetProperties().GetPropertyList())
  468. {
  469. if (p.pid > propid) propid = p.pid;
  470. }
  471. return propid + 1;
  472. }
  473. /**
  474. * Check if a property with this name already exists in the collection of custom properties
  475. *
  476. * @param name the name to check
  477. * @return whether a property with the given name exists in the custom properties
  478. */
  479. public bool Contains(String name)
  480. {
  481. foreach (CT_Property p in props.GetProperties().GetPropertyList())
  482. {
  483. if (p.name.Equals(name)) return true;
  484. }
  485. return false;
  486. }
  487. /**
  488. * Retrieve the custom property with this name, or null if none exists.
  489. *
  490. * You will need to test the various isSetX methods to work out
  491. * what the type of the property is, before fetching the
  492. * appropriate value for it.
  493. *
  494. * @param name the name of the property to fetch
  495. */
  496. public CT_Property GetProperty(String name) {
  497. foreach(CT_Property p in props.GetProperties().GetPropertyList()){
  498. if(p.name.Equals(name)) {
  499. return p;
  500. }
  501. }
  502. return null;
  503. }
  504. }
  505. /**
  506. * Wrapper around the three different kinds of OOXML properties
  507. * and metadata a document can have (Core, Extended and Custom),
  508. * as well Thumbnails.
  509. */
  510. public class POIXMLProperties
  511. {
  512. private OPCPackage pkg;
  513. private CoreProperties core;
  514. private ExtendedProperties ext;
  515. private CustomProperties cust;
  516. private PackagePart extPart;
  517. private PackagePart custPart;
  518. private static ExtendedPropertiesDocument NEW_EXT_INSTANCE;
  519. private static CustomPropertiesDocument NEW_CUST_INSTANCE;
  520. static POIXMLProperties()
  521. {
  522. NEW_EXT_INSTANCE = new ExtendedPropertiesDocument();
  523. NEW_EXT_INSTANCE.AddNewProperties();
  524. NEW_CUST_INSTANCE = new CustomPropertiesDocument();
  525. NEW_CUST_INSTANCE.AddNewProperties();
  526. }
  527. public POIXMLProperties(OPCPackage docPackage)
  528. {
  529. this.pkg = docPackage;
  530. // Core properties
  531. core = new CoreProperties((PackagePropertiesPart)pkg.GetPackageProperties());
  532. // Extended properties
  533. PackageRelationshipCollection extRel =
  534. pkg.GetRelationshipsByType(PackageRelationshipTypes.EXTENDED_PROPERTIES);
  535. if (extRel.Size == 1)
  536. {
  537. extPart = pkg.GetPart(extRel.GetRelationship(0));
  538. ExtendedPropertiesDocument props = ExtendedPropertiesDocument.Parse(
  539. extPart.GetInputStream()
  540. );
  541. ext = new ExtendedProperties(props);
  542. }
  543. else
  544. {
  545. extPart = null;
  546. ext = new ExtendedProperties((ExtendedPropertiesDocument)NEW_EXT_INSTANCE.Copy());
  547. }
  548. // Custom properties
  549. PackageRelationshipCollection custRel =
  550. pkg.GetRelationshipsByType(PackageRelationshipTypes.CUSTOM_PROPERTIES);
  551. if (custRel.Size == 1)
  552. {
  553. custPart = pkg.GetPart(custRel.GetRelationship(0));
  554. CustomPropertiesDocument props = CustomPropertiesDocument.Parse(
  555. custPart.GetInputStream()
  556. );
  557. cust = new CustomProperties(props);
  558. }
  559. else
  560. {
  561. custPart = null;
  562. cust = new CustomProperties((CustomPropertiesDocument)NEW_CUST_INSTANCE.Copy());
  563. }
  564. }
  565. /**
  566. * Returns the core document properties
  567. */
  568. public CoreProperties CoreProperties
  569. {
  570. get
  571. {
  572. return core;
  573. }
  574. }
  575. /**
  576. * Returns the extended document properties
  577. */
  578. public ExtendedProperties ExtendedProperties
  579. {
  580. get
  581. {
  582. return ext;
  583. }
  584. }
  585. /**
  586. * Returns the custom document properties
  587. */
  588. public CustomProperties CustomProperties
  589. {
  590. get
  591. {
  592. return cust;
  593. }
  594. }
  595. /**
  596. * Returns the {@link PackagePart} for the Document
  597. * Thumbnail, or <code>null</code> if there isn't one
  598. *
  599. * @return The Document Thumbnail part or null
  600. */
  601. protected internal PackagePart ThumbnailPart
  602. {
  603. get
  604. {
  605. PackageRelationshipCollection rels =
  606. pkg.GetRelationshipsByType(PackageRelationshipTypes.THUMBNAIL);
  607. if (rels.Size == 1)
  608. {
  609. return pkg.GetPart(rels.GetRelationship(0));
  610. }
  611. return null;
  612. }
  613. }
  614. /**
  615. * Returns the name of the Document thumbnail, eg
  616. * <code>thumbnail.jpeg</code>, or <code>null</code> if there
  617. * isn't one.
  618. *
  619. * @return The thumbnail filename, or null
  620. */
  621. public String ThumbnailFilename
  622. {
  623. get
  624. {
  625. PackagePart tPart = ThumbnailPart;
  626. if (tPart == null) return null;
  627. String name = tPart.PartName.Name;
  628. return name.Substring(name.LastIndexOf('/') + 1);
  629. }
  630. }
  631. /**
  632. * Returns the Document thumbnail image data, or
  633. * <code>null</code> if there isn't one.
  634. *
  635. * @return The thumbnail data, or null
  636. */
  637. public Stream ThumbnailImage
  638. {
  639. get
  640. {
  641. PackagePart tPart = ThumbnailPart;
  642. if (tPart == null) return null;
  643. return tPart.GetInputStream();
  644. }
  645. }
  646. /**
  647. * Sets the Thumbnail for the document, replacing any existing
  648. * one.
  649. *
  650. * @param name The filename for the thumbnail image, eg <code>thumbnail.jpg</code>
  651. * @param imageData The inputstream to read the thumbnail image from
  652. */
  653. public void SetThumbnail(String filename, Stream imageData)
  654. {
  655. PackagePart tPart = ThumbnailPart;
  656. if (tPart == null) {
  657. // New thumbnail
  658. pkg.AddThumbnail(filename, imageData);
  659. } else {
  660. // Change existing
  661. String newType = ContentTypes.GetContentTypeFromFileExtension(filename);
  662. if (!newType.Equals(tPart.ContentType))
  663. {
  664. throw new ArgumentException("Can't set a Thumbnail of type " +
  665. newType + " when existing one is of a different type " +
  666. tPart.ContentType);
  667. }
  668. StreamHelper.CopyStream(imageData, tPart.GetOutputStream());
  669. }
  670. }
  671. /**
  672. * Commit Changes to the underlying OPC namespace
  673. */
  674. public virtual void Commit()
  675. {
  676. if (extPart == null && !NEW_EXT_INSTANCE.ToString().Equals(ext.props.ToString()))
  677. {
  678. try
  679. {
  680. PackagePartName prtname = PackagingUriHelper.CreatePartName("/docProps/app.xml");
  681. pkg.AddRelationship(prtname, TargetMode.Internal, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties");
  682. extPart = pkg.CreatePart(prtname, "application/vnd.openxmlformats-officedocument.extended-properties+xml");
  683. }
  684. catch (InvalidFormatException e)
  685. {
  686. throw new POIXMLException(e);
  687. }
  688. }
  689. if (custPart == null && !NEW_CUST_INSTANCE.ToString().Equals(cust.props.ToString()))
  690. {
  691. try
  692. {
  693. PackagePartName prtname = PackagingUriHelper.CreatePartName("/docProps/custom.xml");
  694. pkg.AddRelationship(prtname, TargetMode.Internal, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties");
  695. custPart = pkg.CreatePart(prtname, "application/vnd.openxmlformats-officedocument.custom-properties+xml");
  696. }
  697. catch (InvalidFormatException e)
  698. {
  699. throw new POIXMLException(e);
  700. }
  701. }
  702. if (extPart != null)
  703. {
  704. Stream out1 = extPart.GetOutputStream();
  705. if (extPart.Size > 0)
  706. extPart.Clear();
  707. ext.props.Save(out1);
  708. out1.Close();
  709. }
  710. if (custPart != null)
  711. {
  712. Stream out1 = custPart.GetOutputStream();
  713. cust.props.Save(out1);
  714. out1.Close();
  715. }
  716. }
  717. }
  718. }