using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; namespace BetterSeewo.Manager.Services { public class CustomConversionService : IConversionService { private static readonly XNamespace A = "http://schemas.openxmlformats.org/drawingml/2006/main"; private static readonly XNamespace P = "http://schemas.openxmlformats.org/presentationml/2006/main"; private static readonly XNamespace R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; private static readonly XNamespace Rel = "http://schemas.openxmlformats.org/package/2006/relationships"; public async Task ConvertPptxToEnbxAsync(string pptxPath) { if (!File.Exists(pptxPath)) throw new FileNotFoundException("PPT 文件未找到", pptxPath); return await Task.Run(() => { var enbxPath = Path.ChangeExtension(pptxPath, ".enbx"); using var pptx = System.IO.Packaging.Package.Open(pptxPath, FileMode.Open, FileAccess.Read); using var enbx = System.IO.Packaging.Package.Open(enbxPath, FileMode.Create, FileAccess.ReadWrite); var pptxPresentation = GetPptxPresentation(pptx); var pptxSlideSize = GetSlideSize(pptxPresentation); var slideWidth = pptxSlideSize.Item1; var slideHeight = pptxSlideSize.Item2; var enbxDoc = new XDocument(); var board = new XElement("DocumentStorageModel"); var slideIds = new List(); var slidesElement = new XElement("Slides"); var slideRels = GetPresentationSlideRels(pptx, pptxPresentation); for (int i = 0; i < slideRels.Count; i++) { var slideId = $"slide_{i + 1}"; slideIds.Add(slideId); var slidePart = pptx.GetPart( System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri(slideRels[i].Item2, UriKind.Relative))); var slideXml = XDocument.Load(slidePart.GetStream()); var slideElement = new XElement("SlideSaveInfo", new XElement("Id", slideId), new XElement("Width", slideWidth), new XElement("Height", slideHeight)); var elementsElement = new XElement("Elements"); var spTree = slideXml.Root?.Element(P + "cSld")?.Element(P + "spTree"); if (spTree != null) { int elemIdx = 0; foreach (var sp in spTree.Elements(P + "sp")) { var xfrm = sp.Element(P + "spPr")?.Element(A + "xfrm"); if (xfrm == null) continue; var off = xfrm.Element(A + "off"); var ext = xfrm.Element(A + "ext"); if (off == null || ext == null) continue; var x = (double)off.Attribute("x"); var y = (double)off.Attribute("y"); var w = (double)ext.Attribute("cx"); var h = (double)ext.Attribute("cy"); var nvSpPr = sp.Element(P + "nvSpPr"); var cNvSpPr = nvSpPr?.Element(P + "cNvSpPr"); var isTextBox = cNvSpPr?.Attribute("txBox") != null; var txBody = sp.Element(P + "txBody"); var isTextShape = txBody != null; if (isTextBox || isTextShape) { var elem = new XElement("TextSaveInfo", new XElement("Id", $"elem_{++elemIdx}"), new XElement("X", x), new XElement("Y", y), new XElement("Width", w), new XElement("Height", h), new XElement("Rotation", 0.0), new XElement("Locked", false)); var bodyPr = txBody?.Element(A + "bodyPr"); var lIns = (double?)bodyPr?.Attribute("lIns") ?? 91440; var rIns = (double?)bodyPr?.Attribute("rIns") ?? 91440; var tIns = (double?)bodyPr?.Attribute("tIns") ?? 91440; var bIns = (double?)bodyPr?.Attribute("bIns") ?? 91440; var richText = new XElement("RichText", new XElement("LeftMargin", lIns), new XElement("RightMargin", rIns), new XElement("TopMargin", tIns), new XElement("BottomMargin", bIns)); if (txBody != null) { foreach (var para in txBody.Elements(A + "p")) { var pElem = new XElement("Paragraph"); foreach (var run in para.Elements(A + "r")) { var rPr = run.Element(A + "rPr"); var text = run.Element(A + "t")?.Value ?? ""; var rElem = new XElement("Run", new XElement("Text", text)); if (rPr != null) { var fmt = new XElement("Formatting"); var sz = rPr.Attribute("sz"); if (sz != null) fmt.Add(new XElement("Size", (double)sz / 100.0)); var b = rPr.Attribute("b"); if (b != null) fmt.Add(new XElement("Bold", (string)b == "1")); var italicAttr = rPr.Attribute("i"); if (italicAttr != null) fmt.Add(new XElement("Italic", (string)italicAttr == "1")); var u = rPr.Attribute("u"); if (u != null) fmt.Add(new XElement("Underline", u.Value)); var srgbClr = rPr?.Element(A + "solidFill")?.Element(A + "srgbClr"); if (srgbClr != null) fmt.Add(new XElement("Color", (string)srgbClr.Attribute("val"))); var latin = rPr?.Element(A + "latin"); if (latin != null) fmt.Add(new XElement("Font", (string)latin.Attribute("typeface"))); if (fmt.HasElements) rElem.Add(fmt); } pElem.Add(rElem); } richText.Add(pElem); } } elem.Add(richText); elementsElement.Add(elem); } } } slideElement.Add(elementsElement); slidesElement.Add(slideElement); } board.Add(new XElement("Board", new XElement("SlideWidth", slideWidth), new XElement("SlideHeight", slideHeight), new XElement("SlideIds", slideIds.Select(id => new XElement("string", id))))); board.Add(slidesElement); enbxDoc.Add(board); var enbxPart = enbx.CreatePart( System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative)), "application/xml"); using (var stream = enbxPart.GetStream()) enbxDoc.Save(stream); enbx.Close(); pptx.Close(); return enbxPath; }); } public async Task ConvertEnbxToPptxAsync(string enbxPath) { if (!File.Exists(enbxPath)) throw new FileNotFoundException("ENBX 文件未找到", enbxPath); return await Task.Run(() => { var pptxPath = Path.ChangeExtension(enbxPath, ".pptx"); using var enbx = System.IO.Packaging.Package.Open(enbxPath, FileMode.Open, FileAccess.Read); using var pptx = System.IO.Packaging.Package.Open(pptxPath, FileMode.Create, FileAccess.ReadWrite); var enbxDocUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative)); var enbxPart = enbx.GetPart(enbxDocUri); var enbxDoc = XDocument.Load(enbxPart.GetStream()); var root = enbxDoc.Root; if (root == null) throw new InvalidDataException("无效的 ENBX 文件"); var board = root.Element("Board"); var slideWidth = (double?)board?.Element("SlideWidth") ?? 12192000; var slideHeight = (double?)board?.Element("SlideHeight") ?? 6858000; var slidesEl = root.Element("Slides"); var slides = slidesEl?.Elements("SlideSaveInfo").ToList() ?? new List(); var contentTypesUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/[Content_Types].xml", UriKind.Relative)); var ctPart = pptx.CreatePart(contentTypesUri, "application/xml"); using (var sw = new StreamWriter(ctPart.GetStream())) { sw.Write(""); sw.Write(""); sw.Write(""); sw.Write(""); sw.Write(""); sw.Write(""); sw.Write(""); sw.Write(""); for (int i = 0; i < slides.Count; i++) sw.Write($""); sw.Write(""); sw.Write(""); sw.Write(""); sw.Write(""); } var pptxPresentation = new XDocument(new XElement(P + "presentation", new XAttribute(XNamespace.Xmlns + "a", A.NamespaceName), new XAttribute(XNamespace.Xmlns + "r", R.NamespaceName), new XAttribute(XNamespace.Xmlns + "p", P.NamespaceName), new XElement(P + "sldMasterIdLst", new XElement(P + "sldMasterId", new XAttribute("id", 2147483648), new XAttribute(R + "id", "rId1"))), new XElement(P + "sldIdLst"), new XElement(P + "sldSz", new XAttribute("cx", slideWidth), new XAttribute("cy", slideHeight)), new XElement(P + "notesSz", new XAttribute("cx", 6858000), new XAttribute("cy", 9144000)))); var presXmlUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/presentation.xml", UriKind.Relative)); var presPart = pptx.CreatePart(presXmlUri, "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"); var presRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/_rels/presentation.xml.rels", UriKind.Relative)); var presRelPart = pptx.CreatePart(presRelUri, "application/vnd.openxmlformats-package.relationships+xml"); var presRelNs = XNamespace.Get(Rel.NamespaceName); var presRels = new XDocument(new XElement(presRelNs + "Relationships", new XElement(presRelNs + "Relationship", new XAttribute("Id", "rId1"), new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"), new XAttribute("Target", "slideMasters/slideMaster1.xml")))); var slideIdLst = pptxPresentation.Root?.Element(P + "sldIdLst"); var nextRelId = 2; var slideRelTargets = new List(); for (int i = 0; i < slides.Count; i++) { var slideId = $"slide_{i + 1}"; var slideFileName = $"slides/slide{i + 1}.xml"; slideRelTargets.Add(slideFileName); slideIdLst?.Add(new XElement(P + "sldId", new XAttribute("id", 256 + i), new XAttribute(R + "id", $"rId{nextRelId + i}"))); presRels.Root?.Add(new XElement("Relationship", new XAttribute("Id", $"rId{nextRelId + i}"), new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"), new XAttribute("Target", slideFileName))); var slideDoc = new XDocument(new XElement(P + "sld", new XAttribute(XNamespace.Xmlns + "a", A.NamespaceName), new XAttribute(XNamespace.Xmlns + "r", R.NamespaceName), new XAttribute(XNamespace.Xmlns + "p", P.NamespaceName))); var cSld = new XElement(P + "cSld"); var spTree = new XElement(P + "spTree"); spTree.Add(new XElement(P + "nvGrpSpPr", new XElement(P + "cNvPr", new XAttribute("id", 1), new XAttribute("name", "")), new XElement(P + "cNvGrpSpPr"))); spTree.Add(new XElement(P + "grpSpPr", new XElement(A + "xfrm", new XElement(A + "off", new XAttribute("x", 0), new XAttribute("y", 0)), new XElement(A + "ext", new XAttribute("cx", 0), new XAttribute("cy", 0)), new XElement(A + "chOff", new XAttribute("x", 0), new XAttribute("y", 0)), new XElement(A + "chExt", new XAttribute("cx", 0), new XAttribute("cy", 0))))); var elements = slides[i].Element("Elements"); if (elements != null) { int shapeId = 2; foreach (var elem in elements.Elements()) { var elemName = elem.Name.LocalName; var x = (double)elem.Element("X"); var y = (double)elem.Element("Y"); var w = (double)elem.Element("Width"); var h = (double)elem.Element("Height"); var rotation = (double?)elem.Element("Rotation") ?? 0.0; if (elemName == "TextSaveInfo") { var sp = new XElement(P + "sp"); sp.Add(new XElement(P + "nvSpPr", new XElement(P + "cNvPr", new XAttribute("id", shapeId++), new XAttribute("name", $"TextBox{shapeId - 1}")), new XElement(P + "cNvSpPr", new XAttribute("txBox", 1)), new XElement(P + "nvPr"))); var spPr = new XElement(P + "spPr", new XElement(A + "xfrm", new XElement(A + "off", new XAttribute("x", x), new XAttribute("y", y)), new XElement(A + "ext", new XAttribute("cx", w), new XAttribute("cy", h))), new XElement(A + "prstGeom", new XAttribute("prst", "rect"), new XElement(A + "avLst"))); sp.Add(spPr); var txBody = new XElement(P + "txBody", new XElement(A + "bodyPr", new XAttribute("wrap", "square"))); var richText = elem.Element("RichText"); if (richText != null) { var leftMargin = (double?)richText.Element("LeftMargin") ?? 91440; var rightMargin = (double?)richText.Element("RightMargin") ?? 91440; var topMargin = (double?)richText.Element("TopMargin") ?? 91440; var bottomMargin = (double?)richText.Element("BottomMargin") ?? 91440; var bodyPr = txBody.Element(A + "bodyPr"); if (bodyPr != null) { bodyPr.Add(new XAttribute("lIns", leftMargin)); bodyPr.Add(new XAttribute("rIns", rightMargin)); bodyPr.Add(new XAttribute("tIns", topMargin)); bodyPr.Add(new XAttribute("bIns", bottomMargin)); } var paragraphs = richText.Elements("Paragraph"); if (!paragraphs.Any()) { txBody.Add(new XElement(A + "p")); } else { foreach (var para in paragraphs) { var pElem = new XElement(A + "p"); var runs = para.Elements("Run"); if (!runs.Any()) { pElem.Add(new XElement(A + "endParaRPr")); } else { foreach (var run in runs) { var text = (string)run.Element("Text") ?? ""; var rElem = new XElement(A + "r"); var fmt = run.Element("Formatting"); if (fmt != null) { var rPr = new XElement(A + "rPr"); var sz = fmt.Element("Size"); if (sz != null) rPr.Add(new XAttribute("sz", (double)sz * 100)); var bold = fmt.Element("Bold"); if (bold != null && (bool)bold) rPr.Add(new XAttribute("b", 1)); var italic = fmt.Element("Italic"); if (italic != null && (bool)italic) rPr.Add(new XAttribute("i", 1)); var underline = (string?)fmt.Element("Underline"); if (underline != null) rPr.Add(new XAttribute("u", underline)); var color = (string?)fmt.Element("Color"); if (color != null) rPr.Add(new XElement(A + "solidFill", new XElement(A + "srgbClr", new XAttribute("val", color)))); var font = (string?)fmt.Element("Font"); if (font != null) rPr.Add(new XElement(A + "latin", new XAttribute("typeface", font))); if (rPr.HasAttributes || rPr.HasElements) rElem.Add(rPr); } rElem.Add(new XElement(A + "t", text)); pElem.Add(rElem); } } txBody.Add(pElem); } } } else { txBody.Add(new XElement(A + "p")); } sp.Add(txBody); spTree.Add(sp); } } } cSld.Add(spTree); slideDoc.Root?.Add(cSld); var slideUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri($"/ppt/{slideFileName}", UriKind.Relative)); var slidePart = pptx.CreatePart(slideUri, "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"); using (var sw = new StreamWriter(slidePart.GetStream())) slideDoc.Save(sw); var slideRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri($"/ppt/slides/_rels/slide{i + 1}.xml.rels", UriKind.Relative)); var slideRelPart = pptx.CreatePart(slideRelUri, "application/vnd.openxmlformats-package.relationships+xml"); using (var sw = new StreamWriter(slideRelPart.GetStream())) { sw.Write(""); sw.Write(""); sw.Write(""); } } var rootRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative)); var rootRelPart = pptx.CreatePart(rootRelUri, "application/vnd.openxmlformats-package.relationships+xml"); using (var sw = new StreamWriter(rootRelPart.GetStream())) { sw.Write(""); sw.Write(""); sw.Write(""); } using (var sw = new StreamWriter(presPart.GetStream())) pptxPresentation.Save(sw); using (var sw = new StreamWriter(presRelPart.GetStream())) presRels.Save(sw); CreateMinimalTheme(pptx); CreateMinimalSlideMaster(pptx); CreateMinimalSlideLayout(pptx); CreatePresProps(pptx); CreateViewProps(pptx); enbx.Close(); pptx.Close(); return pptxPath; }); } public Task FixConversionErrorsAsync() { var en5Path = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Seewo", "EasiNote5"); var tempDir = Path.Combine(en5Path, "Temp", "Conversion"); if (Directory.Exists(tempDir)) { try { Directory.Delete(tempDir, true); } catch { } } return Task.CompletedTask; } private static Tuple GetSlideSize(XDocument presentation) { var sldSz = presentation.Root?.Element(P + "sldSz"); if (sldSz == null) return Tuple.Create(12192000.0, 6858000.0); return Tuple.Create( (double)sldSz.Attribute("cx"), (double)sldSz.Attribute("cy")); } private static XDocument GetPptxPresentation(System.IO.Packaging.Package pptx) { var relsUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative)); var relsPart = pptx.GetPart(relsUri); var rels = XDocument.Load(relsPart.GetStream()); var officeRel = rels.Root?.Elements("Relationship") .FirstOrDefault(r => (string)r.Attribute("Type") == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"); if (officeRel == null) throw new InvalidDataException("未找到 Office Document 关系"); var presUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri((string)officeRel.Attribute("Target"), UriKind.Relative)); var presPart = pptx.GetPart(presUri); return XDocument.Load(presPart.GetStream()); } private static List> GetPresentationSlideRels(System.IO.Packaging.Package pptx, XDocument presentation) { var relsUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/_rels/presentation.xml.rels", UriKind.Relative)); var relsPart = pptx.GetPart(relsUri); var rels = XDocument.Load(relsPart.GetStream()); var slideType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"; return rels.Root?.Elements("Relationship") .Where(r => (string)r.Attribute("Type") == slideType) .Select(r => Tuple.Create((string)r.Attribute("Id"), (string)r.Attribute("Target"))) .ToList() ?? new List>(); } private static void CreateMinimalTheme(System.IO.Packaging.Package pptx) { var themeUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/theme/theme1.xml", UriKind.Relative)); var themePart = pptx.CreatePart(themeUri, "application/vnd.openxmlformats-officedocument.theme+xml"); using (var sw = new StreamWriter(themePart.GetStream())) { sw.Write(""); sw.Write(""); sw.Write(""); sw.Write(""); sw.Write(""); sw.Write(""); } } private static void CreateMinimalSlideMaster(System.IO.Packaging.Package pptx) { var masterUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/slideMasters/slideMaster1.xml", UriKind.Relative)); var masterPart = pptx.CreatePart(masterUri, "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"); using (var sw = new StreamWriter(masterPart.GetStream())) { sw.Write(""); sw.Write(""); sw.Write(""); } var masterRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/slideMasters/_rels/slideMaster1.xml.rels", UriKind.Relative)); var masterRelPart = pptx.CreatePart(masterRelUri, "application/vnd.openxmlformats-package.relationships+xml"); using (var sw = new StreamWriter(masterRelPart.GetStream())) { sw.Write(""); sw.Write(""); sw.Write(""); } } private static void CreateMinimalSlideLayout(System.IO.Packaging.Package pptx) { var layoutUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/slideLayouts/slideLayout1.xml", UriKind.Relative)); var layoutPart = pptx.CreatePart(layoutUri, "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"); using (var sw = new StreamWriter(layoutPart.GetStream())) { sw.Write(""); sw.Write(""); } } private static void CreatePresProps(System.IO.Packaging.Package pptx) { var uri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/presProps.xml", UriKind.Relative)); var part = pptx.CreatePart(uri, "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"); using (var sw = new StreamWriter(part.GetStream())) sw.Write(""); } private static void CreateViewProps(System.IO.Packaging.Package pptx) { var uri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/viewProps.xml", UriKind.Relative)); var part = pptx.CreatePart(uri, "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml"); using (var sw = new StreamWriter(part.GetStream())) sw.Write(""); } } }