我想生成将由我的经纪人在他的iPad上打开时,他走出办公室.NET的电子表格。
电子表格打开罚款在Windows PC上,而是试图在iPad上打开时,它说:“当读取文件时出错”(非常有用!)
通过使用与不开放在iPad上的文档的OpenXML的SDK生产力工具中的“比较”功能,并通过执行错误文件的XML文件中的一些手工编辑在记事本中我已经把范围缩小到文件XL / _rels /工作簿.xml.rels存储在工作簿中的部件之间的关系。
这是我使用生成WorkbookPart和引用代码
WorkbookPart workbookPart1 = document.AddWorkbookPart();
WorkbookStylesPart workbookStylesPart1 = workbookPart1.AddNewPart<WorkbookStylesPart>("rId3");
ThemePart themePart1 = workbookPart1.AddNewPart<ThemePart>("rId2");
WorksheetPart worksheetPart1 = workbookPart1.AddNewPart<WorksheetPart>("rId1");
我的代码生成以下输出,这并不在iPad上打开。
<?xml version="1.0" encoding="utf-8" ?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="/xl/styles.xml" Id="rId3" />
<Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="/xl/theme/theme.xml" Id="rId2" />
<Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="/xl/worksheets/sheet.xml" Id="rId1" />
</Relationships>
如果我更改目标属性的值使用相对参考路径,给下面的输出,那么它在iPad上打开。
<?xml version="1.0" encoding="utf-8" ?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml" Id="rId3" />
<Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme.xml" Id="rId2" />
<Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet.xml" Id="rId1" />
</Relationships>
所以,问题是:
如何更改我的.NET代码,使得它输出XML的第二个版本,与相对路径。
所有帮助感激地接受!
Answer 1:
我花了很多时间研究这个,我想我会分享我的结果。 看来,OpenXML的做两件事情。 1. content_types.xml文件丢失工作簿2. XL / _rels / workbook.xml.rels文件中的条目是使用fullly相对路径。
Excel中打开自己的文件很好,但我已经试过在iPad上的应用程序不同,他们都失败。 所以,我不得不使用下面的代码来手动修复文件自己。 它假设该文件的全部内容传过来的数据流,并使用DotNetZip来打开和操作。 希望这个代码可以帮助别人!
private Stream ApplyOpenXmlFix(Stream input)
{
const string RELS_FILE = @"xl/_rels/workbook.xml.rels";
const string RELATIONSHIP_ELEMENT = "Relationship";
const string CONTENT_TYPE_FILE = @"[Content_Types].xml";
const string XL_WORKBOOK_XML = "/xl/workbook.xml";
const string TARGET_ATTRIBUTE = "Target";
const string SUPERFLUOUS_PATH = "/xl/";
const string OVERRIDE_ELEMENT = "Override";
const string PARTNAME_ATTRIBUTE = "PartName";
const string CONTENTTYPE_ATTRIBUTE = "ContentType";
const string CONTENTTYPE_VALUE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
XNamespace contentTypesNamespace = "http://schemas.openxmlformats.org/package/2006/content-types";
XNamespace relsNamespace = "http://schemas.openxmlformats.org/package/2006/relationships";
XDocument xlDocument;
MemoryStream memWriter;
try
{
input.Seek(0, SeekOrigin.Begin);
ZipFile zip = ZipFile.Read(input);
//First we fix the workbook relations file
var workbookRelations = zip.Entries.Where(e => e.FileName == RELS_FILE).Single();
xlDocument = XDocument.Load(workbookRelations.OpenReader());
//Remove the /xl/ relative path from all target attributes
foreach (var relationship in xlDocument.Root.Elements(relsNamespace + RELATIONSHIP_ELEMENT))
{
var target = relationship.Attribute(TARGET_ATTRIBUTE);
if (target != null && target.Value.StartsWith(SUPERFLUOUS_PATH))
{
target.Value = target.Value.Substring(SUPERFLUOUS_PATH.Length);
}
}
//Replace the content in the source zip file
memWriter = new MemoryStream();
xlDocument.Save(memWriter, SaveOptions.DisableFormatting);
memWriter.Seek(0, SeekOrigin.Begin);
zip.UpdateEntry(RELS_FILE, memWriter);
//Now we fix the content types XML file
var contentTypeEntry = zip.Entries.Where(e => e.FileName == CONTENT_TYPE_FILE).Single();
xlDocument = XDocument.Load(contentTypeEntry.OpenReader());
if (!xlDocument.Root.Elements().Any(e =>
e.Name == contentTypesNamespace + OVERRIDE_ELEMENT &&
e.Attribute(PARTNAME_ATTRIBUTE) != null &&
e.Attribute(PARTNAME_ATTRIBUTE).Value == XL_WORKBOOK_XML))
{
//Add in the missing element
var overrideElement = new XElement(
contentTypesNamespace + OVERRIDE_ELEMENT,
new XAttribute(PARTNAME_ATTRIBUTE, XL_WORKBOOK_XML),
new XAttribute(CONTENTTYPE_ATTRIBUTE, CONTENTTYPE_VALUE));
xlDocument.Root.Add(overrideElement);
//Replace the content
memWriter = new MemoryStream();
xlDocument.Save(memWriter, SaveOptions.DisableFormatting);
memWriter.Seek(0, SeekOrigin.Begin);
zip.UpdateEntry(CONTENT_TYPE_FILE, memWriter);
}
Stream output = new MemoryStream();
//Save file
zip.Save(output);
return output;
}
catch
{
//Just in case it fails, return the original document
return input;
}
}
Answer 2:
发送PDF格式的Comradsky的回答是一个好主意,但如果有人需要能够解决这个问题,我想出了一个解决方案。 我知道这是一个可怕的黑客,但它的工作原理,我已经花了几个小时试图找到一种方式来做到这一点“合法”地无济于事。
它涉及到打开的.rels文件并直接编辑文件中的XML文档已被关闭后。
public static void MakeRelativePaths(string filepath)
{
// Get the namespace strings
const string documentRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
const string relationshipSchema = "http://schemas.openxmlformats.org/package/2006/relationships";
string documentUri = null;
string documentDirectory = null;
string documentName = null;
Uri relDocUri = null;
XName targetAttributeName = null;
string targetValue = null;
// Open the package
using (Package xlPackage = Package.Open(filepath, FileMode.Open, FileAccess.ReadWrite))
{
// Get the directory and filename of the main document part (e.g. /xl/workbook.xml).
foreach (System.IO.Packaging.PackageRelationship relationship in xlPackage.GetRelationshipsByType(documentRelationshipType))
{
documentUri = relationship.TargetUri.ToString();
documentName = System.IO.Path.GetFileName(documentUri);
documentDirectory = documentUri.Substring(0, documentUri.Length - documentName.Length);
// There should only be document part in the package, but break out anyway.
break;
}
// Load the relationship document
relDocUri = new Uri(documentDirectory + "_rels/" + documentName + ".rels", UriKind.Relative);
XDocument relDoc = XDocument.Load(xlPackage.GetPart(relDocUri).GetStream());
// Loop through all of the relationship nodes
targetAttributeName = XName.Get("Target");
foreach (XElement relNode in relDoc.Elements(XName.Get("Relationships", relationshipSchema)).Elements(XName.Get("Relationship", relationshipSchema)))
{
// Edit the value of the Target attribute
targetValue = relNode.Attribute(targetAttributeName).Value;
if (targetValue.StartsWith(documentDirectory))
targetValue = targetValue.Substring(documentDirectory.Length);
relNode.Attribute(targetAttributeName).Value = targetValue;
}
// Save the document
relDoc.Save(xlPackage.GetPart(relDocUri).GetStream());
}
}
Answer 3:
我也一直在努力用类似这一段时间的问题。 我终于想出了一个可行的解决方案。 这是我写来解决这个问题的代码
// Add a new worksheet part to the workbook.
WorksheetPart newWorksheetPart = _document.WorkbookPart.AddNewPart<WorksheetPart>();
newWorksheetPart.Worksheet = new DocumentFormat.OpenXml.Spreadsheet.Worksheet(new SheetData());
Sheets sheets = _document.WorkbookPart.Workbook.GetFirstChild<Sheets>();
string relationshipId = _document.WorkbookPart.GetIdOfPart(newWorksheetPart);
//This bit is required for iPad to be able to read the sheets inside the xlsx file. The file will still work fine in Excel
string relationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
_document.Package.GetPart(_document.WorkbookPart.Uri).CreateRelationship(new Uri(newWorksheetPart.Uri.OriginalString.Replace("/xl/", String.Empty).Trim(), UriKind.Relative), TargetMode.Internal, relationshipType);
_document.Package.GetPart(_document.WorkbookPart.Uri).DeleteRelationship(relationshipId);
PackageRelationshipCollection sheetRelationships = _document.Package.GetPart(_document.WorkbookPart.Uri).GetRelationshipsByType(relationshipType);
relationshipId = sheetRelationships.Where(f => f.TargetUri.OriginalString == newWorksheetPart.Uri.OriginalString.Replace("/xl/", String.Empty).Trim()).Single().Id;
// Get a unique ID for the new sheet.
uint sheetId = 1;
if (sheets.Elements<Sheet>().Count() > 0)
sheetId = sheets.Elements<Sheet>().Max(s => s.SheetId.Value) + 1;
// Append the new worksheet and associate it with the workbook.
Sheet sheet = new Sheet() { Id = relationshipId, SheetId = sheetId, Name = sheetName };
sheets.Append(sheet);
_worksheets.Add(new Worksheet(newWorksheetPart.Worksheet, sheetId));
_document和_worksheets,都在我的解决方案类的私有变量。
Answer 4:
你可以尝试一旦创建它来验证OpenXML的电子表格:
using System;
using System.Collections.Generic;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Validation;
using (OpenXmlPackage document = SpreadsheetDocument.Open(spreadsheetPathToValidate, false))
{
var validator = new OpenXmlValidator();
IEnumerable<ValidationErrorInfo> errors = validator.Validate(document);
foreach (ValidationErrorInfo info in errors)
{
try
{
Console.WriteLine("Validation information: {0} {1} in {2} part (path {3}): {4}",
info.ErrorType,
info.Node.GetType().Name,
info.Part.Uri,
info.Path.XPath,
info.Description);
}
catch (Exception ex)
{
Console.WriteLine("Validation failed: {0}", ex);
}
}
}
希望帮助,
Answer 5:
我遇到过同样的问题。 在CellReference财产填充在细胞后,它开始为我工作。 在CellReference你只是把像“A1”,“B1”,......,“C123”的单元的名称
文章来源: OpenXML spreadsheet created in .NET won't open in iPad