blob: 7595de6de9b5c3523efc196bacd12b95d146a5a0 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "DFPlatform.h"
#include "WordPackage.h"
#include "WordStyles.h"
#include "WordSettings.h"
#include "DFDOM.h"
#include "DFFilesystem.h"
#include "DFXML.h"
#include "OPC.h"
#include "DFHTML.h"
#include "WordField.h"
#include "WordBookmark.h"
#include "DFCommon.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
static DFDocument *parsePart(WordPackage *package, OPCPart *part, DFError **error);
// RSIDs are used to associated particular pargraphs, runs, and other components of the document
// with distinct editing sessions to facilitate merging. Keeping them up to date in an appropriate
// manner would for us be more work than necessary, and there's better ways to do merging anyway
// (see: any version control system). It's safe to remove these from the document entirely.
// See: http://blogs.msdn.com/b/brian_jones/archive/2006/12/11/what-s-up-with-all-those-rsids.aspx
static void Word_stripRSIDsRecursive(DFNode *node)
{
if (node->tag >= MIN_ELEMENT_TAG) {
Tag remove[20];
unsigned int removeCount = 0;
for (unsigned int i = 0; i < node->attrsCount; i++) {
switch (node->attrs[i].tag) {
case WORD_RSID:
case WORD_RSIDDEL:
case WORD_RSIDP:
case WORD_RSIDR:
case WORD_RSIDRDEFAULT:
case WORD_RSIDROOT:
case WORD_RSIDRPR:
case WORD_RSIDS:
case WORD_RSIDSECT:
case WORD_RSIDTR:
assert(removeCount+1 < 20);
remove[removeCount++] = node->attrs[i].tag;
break;
}
}
for (unsigned int i = 0; i < removeCount; i++)
DFRemoveAttribute(node,remove[i]);
}
for (DFNode *child = node->first; child != NULL; child = child->next) {
Word_stripRSIDsRecursive(child);
}
}
static void Word_stripRSIDs(WordPackage *package)
{
if (package->document != NULL)
Word_stripRSIDsRecursive(package->document->docNode);
if (package->numbering != NULL)
Word_stripRSIDsRecursive(package->numbering->docNode);
if (package->styles != NULL)
Word_stripRSIDsRecursive(package->styles->docNode);
if (package->settings != NULL)
Word_stripRSIDsRecursive(package->settings->docNode);
if (package->settings != NULL) {
DFNode *rsids = DFChildWithTag(package->settings->root,WORD_RSIDS);
if (rsids != NULL)
DFRemoveNode(rsids);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// WordPackage //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////
WordPackage *WordPackageRetain(WordPackage *package)
{
if (package != NULL)
package->retainCount++;
return package;
}
void WordPackageRelease(WordPackage *package)
{
if ((package == NULL) || (--package->retainCount > 0))
return;
DFDocumentRelease(package->document);
DFDocumentRelease(package->numbering);
DFDocumentRelease(package->styles);
DFDocumentRelease(package->settings);
DFDocumentRelease(package->theme);
DFDocumentRelease(package->footnotes);
DFDocumentRelease(package->endnotes);
OPCPackageFree(package->opc);
free(package);
}
void WordPackageSetDocument(WordPackage *package, DFDocument *document)
{
DFDocumentRetain(document);
DFDocumentRelease(package->document);
package->document = document;
}
void WordPackageSetNumbering(WordPackage *package, DFDocument *numbering)
{
DFDocumentRetain(numbering);
DFDocumentRelease(package->numbering);
package->numbering = numbering;
}
void WordPackageSetStyles(WordPackage *package, DFDocument *styles)
{
DFDocumentRetain(styles);
DFDocumentRelease(package->styles);
package->styles = styles;
}
void WordPackageSetSettings(WordPackage *package, DFDocument *settings)
{
DFDocumentRetain(settings);
DFDocumentRelease(package->settings);
package->settings = settings;
}
void WordPackageSetTheme(WordPackage *package, DFDocument *theme)
{
DFDocumentRetain(theme);
DFDocumentRelease(package->theme);
package->theme = theme;
}
void WordPackageSetFootnotes(WordPackage *package, DFDocument *footnotes)
{
DFDocumentRetain(footnotes);
DFDocumentRelease(package->footnotes);
package->footnotes = footnotes;
}
void WordPackageSetEndnotes(WordPackage *package, DFDocument *endnotes)
{
DFDocumentRetain(endnotes);
DFDocumentRelease(package->endnotes);
package->endnotes = endnotes;
}
static void addMissingParts(WordPackage *package)
{
if (package->styles == NULL)
package->styles = DFDocumentNewWithRoot(WORD_STYLES);
if (package->settings == NULL)
package->settings = DFDocumentNewWithRoot(WORD_SETTINGS);
if (package->footnotes == NULL)
package->footnotes = DFDocumentNewWithRoot(WORD_FOOTNOTES);
if (package->endnotes == NULL)
package->endnotes = DFDocumentNewWithRoot(WORD_ENDNOTES);
}
WordPackage *WordPackageOpenNew(DFStorage *storage, DFError **error)
{
if (DFStorageFormat(storage) != DFFileFormatDocx) {
DFErrorFormat(error,"Incorrect format: Expected %s, got %s",
DFFileFormatToExtension(DFFileFormatDocx),DFFileFormatToExtension(DFStorageFormat(storage)));
return NULL;
}
OPCPackage *opc = OPCPackageOpenNew(storage,error);
if (opc == NULL)
return NULL;
int ok = 0;
WordPackage *package = (WordPackage *)xcalloc(1,sizeof(WordPackage));
package->retainCount = 1;
package->opc = opc;
package->documentPart = OPCPackagePartWithURI(package->opc,"/word/document.xml");
package->document = DFDocumentNewWithRoot(WORD_DOCUMENT);
DFAppendChild(package->document->root,DFCreateElement(package->document,WORD_BODY));
OPCRelationshipSetAddId(package->opc->relationships,"rId1",WORDREL_OFFICE_DOCUMENT,package->documentPart->URI,0);
OPCContentTypesSetOverride(package->opc->contentTypes,package->documentPart->URI,WORDTYPE_OFFICE_DOCUMENT);
// OPCContentTypesSetDefault(package->_opc->contentTypes,"xml","application/xml");
OPCContentTypesSetDefault(package->opc->contentTypes,"rels","application/vnd.openxmlformats-package.relationships+xml");
// OPCContentTypesSetDefault(package->_opc->contentTypes,"jpeg","image/jpeg");
addMissingParts(package);
ok = 1;
if (ok)
return package;
WordPackageRelease(package);
return NULL;
}
WordPackage *WordPackageOpenFrom(DFStorage *storage, DFError **error)
{
if (DFStorageFormat(storage) != DFFileFormatDocx) {
DFErrorFormat(error,"Incorrect format: Expected %s, got %s",
DFFileFormatToExtension(DFFileFormatDocx),DFFileFormatToExtension(DFStorageFormat(storage)));
return NULL;
}
OPCPackage *opc = OPCPackageOpenFrom(storage,error);
if (opc == NULL)
return NULL;
int ok = 0;
WordPackage *package = (WordPackage *)xcalloc(1,sizeof(WordPackage));
package->retainCount = 1;
package->opc = opc;
OPCRelationship *rel;
rel = OPCRelationshipSetLookupByType(package->opc->relationships,WORDREL_OFFICE_DOCUMENT);
if (rel != NULL)
package->documentPart = OPCPackagePartWithURI(package->opc,rel->target);
if (package->documentPart == NULL) {
DFErrorFormat(error,"Document part not found");
goto end;
}
assert(package->document == NULL);
assert(package->numbering == NULL);
assert(package->styles == NULL);
assert(package->settings == NULL);
assert(package->theme == NULL);
assert(package->footnotes == NULL);
assert(package->endnotes == NULL);
if ((package->document = parsePart(package,package->documentPart,error)) == NULL)
goto end;
// Numbering
rel = OPCRelationshipSetLookupByType(package->documentPart->relationships,WORDREL_NUMBERING);
OPCPart *numberingPart = (rel != NULL) ? OPCPackagePartWithURI(package->opc,rel->target) : NULL;
if (numberingPart != NULL) {
if ((package->numbering = parsePart(package,numberingPart,error)) == NULL)
goto end;
}
// Styles
rel = OPCRelationshipSetLookupByType(package->documentPart->relationships,WORDREL_STYLES);
OPCPart *stylesPart = (rel != NULL) ? OPCPackagePartWithURI(package->opc,rel->target) : NULL;
if (stylesPart != NULL) {
if ((package->styles = parsePart(package,stylesPart,error)) == NULL)
goto end;
}
// Settings
rel = OPCRelationshipSetLookupByType(package->documentPart->relationships,WORDREL_SETTINGS);
OPCPart *settingsPart = (rel != NULL) ? OPCPackagePartWithURI(package->opc,rel->target) : NULL;
if (settingsPart != NULL) {
if ((package->settings = parsePart(package,settingsPart,error)) == NULL)
goto end;
}
// Theme
rel = OPCRelationshipSetLookupByType(package->documentPart->relationships,WORDREL_THEME);
OPCPart *themePart = (rel != NULL) ? OPCPackagePartWithURI(package->opc,rel->target) : NULL;
if (themePart != NULL) {
if ((package->theme = parsePart(package,themePart,error)) == NULL)
goto end;
}
// Footnotes
rel = OPCRelationshipSetLookupByType(package->documentPart->relationships,WORDREL_FOOTNOTES);
OPCPart *footnotesPart = (rel != NULL) ? OPCPackagePartWithURI(package->opc,rel->target) : NULL;
if (footnotesPart != NULL) {
if ((package->footnotes = parsePart(package,footnotesPart,error)) == NULL)
goto end;
}
// Endnotes
rel = OPCRelationshipSetLookupByType(package->documentPart->relationships,WORDREL_ENDNOTES);
OPCPart *endnotesPart = (rel != NULL) ? OPCPackagePartWithURI(package->opc,rel->target) : NULL;
if (endnotesPart != NULL) {
if ((package->endnotes = parsePart(package,endnotesPart,error)) == NULL)
goto end;
}
addMissingParts(package);
Word_stripRSIDs(package);
ok = 1;
end:
if (ok)
return package;
WordPackageRelease(package);
return NULL;
}
static int serializePart(WordPackage *package, DFDocument *doc, OPCPart *part, DFError **error)
{
char *str = DFSerializeXMLString(doc,0,0);
int ok = OPCPackageWritePart(package->opc,str,strlen(str),part,error);
free(str);
return ok;
}
static int savePart(WordPackage *package, DFDocument *document,
const char *relName, const char *typeName,
const char *path, DFError **error)
{
OPCRelationship *rel = OPCRelationshipSetLookupByType(package->documentPart->relationships,relName);
OPCPart *part = (rel != NULL) ? OPCPackagePartWithURI(package->opc,rel->target) : NULL;
if ((document != NULL) && (document->root->first != NULL)) {
if (part == NULL)
part = OPCPackageAddRelatedPart(package->opc,path,typeName,relName,package->documentPart);
if (!serializePart(package,document,part,error))
return 0;
}
else {
if (part != NULL) {
OPCPackageDeletePart(package->opc,part,error);
OPCPackageRemoveRelatedPart(package->opc,part->URI,relName,package->documentPart);
}
}
return 1;
}
int WordPackageSave(WordPackage *package, DFError **error)
{
// Document
assert(package->document != NULL);
assert(package->documentPart != NULL);
if ((package->document != NULL) && !serializePart(package,package->document,package->documentPart,error))
return 0;
// Numbering
if (!savePart(package,package->numbering,WORDREL_NUMBERING,WORDTYPE_NUMBERING,"/word/numbering.xml",error))
return 0;
// Styles
if (!savePart(package,package->styles,WORDREL_STYLES,WORDTYPE_STYLES,"/word/styles.xml",error))
return 0;
// Settings
if (!savePart(package,package->settings,WORDREL_SETTINGS,WORDTYPE_SETTINGS,"/word/settings.xml",error))
return 0;
// Theme
if (!savePart(package,package->theme,WORDREL_THEME,WORDTYPE_THEME,"/word/theme.xml",error))
return 0;
// Footnotes
if (!savePart(package,package->footnotes,WORDREL_FOOTNOTES,WORDTYPE_FOOTNOTES,"/word/footnotes.xml",error))
return 0;
// Endnotes
if (!savePart(package,package->endnotes,WORDREL_ENDNOTES,WORDTYPE_ENDNOTES,"/word/endnotes.xml",error))
return 0;
// Build .docx zip archive, if requested
if (!OPCPackageSave(package->opc,error))
return 0;
return 1;
}
static DFDocument *parsePart(WordPackage *package, OPCPart *part, DFError **error)
{
DFBuffer *content = OPCPackageReadPart(package->opc,part,error);
if (content == NULL)
return NULL;;
DFDocument *doc = DFParseXMLString(content->data,error);
DFBufferRelease(content);
return doc;
}
const char *WordPackageTargetForDocumentRel(WordPackage *package, const char *relId)
{
OPCRelationship *rel = OPCRelationshipSetLookupById(package->documentPart->relationships,relId);
if (rel != NULL)
return rel->target;
else
return NULL;
}
int WordPackageSimplifyFields(WordPackage *package)
{
return Word_simplifyFields(package);
}
void WordPackageCollapseBookmarks(WordPackage *package)
{
WordBookmarks_collapseNew(package->document);
}
void WordPackageExpandBookmarks(WordPackage *package)
{
WordBookmarks_expandNew(package->document);
}