using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.IO.Packaging; using System.Linq; using System.Xml.Linq; namespace BetterEN5.Services.Conversion { public class EnbxReader { private int _nextId; public SmfDocument Import(string enbxPath) { using var package = Package.Open(enbxPath, FileMode.Open, FileAccess.Read); return Import(package); } public SmfDocument Import(Package enbx) { _nextId = 1; var doc = new SmfDocument(); ExtractMediaFiles(enbx, doc); var docPart = FindDocumentPart(enbx); if (docPart != null) { var docXml = XDocument.Load(docPart.GetStream()); ParseDocumentXml(docXml, doc, enbx); } else { var mainDocPart = enbx.GetPart( PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative))); var mainDoc = XDocument.Load(mainDocPart.GetStream()); ParseSlideBasedFormat(mainDoc, doc, enbx); } return doc; } private void ExtractMediaFiles(Package enbx, SmfDocument doc) { foreach (var part in enbx.GetParts()) { var uri = part.Uri.ToString(); if (uri.StartsWith("/media/", StringComparison.OrdinalIgnoreCase) || uri.StartsWith("/ppt/media/", StringComparison.OrdinalIgnoreCase)) { var fileName = Path.GetFileName(uri); using var stream = part.GetStream(); var data = new byte[stream.Length]; stream.Read(data, 0, data.Length); doc.MediaFiles[fileName] = data; doc.MediaContentTypes[fileName] = part.ContentType; } } } private PackagePart? FindDocumentPart(Package enbx) { var relsUri = PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative)); if (!enbx.PartExists(relsUri)) return null; var relsPart = enbx.GetPart(relsUri); var rels = XDocument.Load(relsPart.GetStream()); foreach (var rel in rels.Root?.Elements("Relationship") ?? Enumerable.Empty()) { var type = (string)rel.Attribute("Type") ?? ""; if (type.Contains("document") || type.Contains("officeDocument")) { var target = (string)rel.Attribute("Target") ?? ""; var targetUri = PackUriHelper.CreatePartUri(new Uri(target, UriKind.Relative)); if (enbx.PartExists(targetUri)) return enbx.GetPart(targetUri); } } return null; } private void ParseDocumentXml(XElement docXml, SmfDocument doc, Package enbx) { var root = docXml.Root ?? docXml; var enNs = root.GetDefaultNamespace(); var props = root.Element(enNs + "Properties"); if (props != null) { var sw = (string?)props.Element(enNs + "SlideWidth"); var sh = (string?)props.Element(enNs + "SlideHeight"); doc.Properties.SlideWidth = sw != null ? double.Parse(sw, CultureInfo.InvariantCulture) : 12192000; doc.Properties.SlideHeight = sh != null ? double.Parse(sh, CultureInfo.InvariantCulture) : 6858000; doc.Properties.Title = (string?)props.Element(enNs + "Title") ?? ""; doc.Properties.Author = (string?)props.Element(enNs + "Author") ?? ""; } var board = root.Element("Board"); if (board != null) { doc.Properties.SlideWidth = (double?)board.Element("SlideWidth") ?? 12192000; doc.Properties.SlideHeight = (double?)board.Element("SlideHeight") ?? 6858000; } var slidesEl = root.Element(enNs + "Slides") ?? root.Element("Slides"); if (slidesEl != null) { int slideNum = 0; foreach (var slideRef in slidesEl.Elements(enNs + "SlideRef")) { slideNum++; var target = (string)slideRef ?? $"slides/slide{slideNum}.xml"; var slidePart = FindSlidePart(enbx, target, slideNum); if (slidePart != null) { var slideXml = XDocument.Load(slidePart.GetStream()); ParseSlideXml(slideXml, doc, slideNum); } } } } private void ParseSlideBasedFormat(XDocument mainDoc, SmfDocument doc, Package enbx) { var root = mainDoc.Root; if (root == null) return; var board = root.Element("Board"); if (board != null) { doc.Properties.SlideWidth = (double?)board.Element("SlideWidth") ?? 12192000; doc.Properties.SlideHeight = (double?)board.Element("SlideHeight") ?? 6858000; } var slidesEl = root.Element("Slides"); if (slidesEl != null) { int idx = 0; foreach (var slideEl in slidesEl.Elements("SlideSaveInfo")) { idx++; var slide = new SmfSlide { Id = (string)slideEl.Element("Id") ?? $"slide_{idx}", }; ParseLegacyElements(slideEl, slide); doc.Slides.Add(slide); } } } private void ParseSlideXml(XDocument slideXml, SmfDocument doc, int slideNum) { var root = slideXml.Root ?? slideXml; var enNs = root.GetDefaultNamespace(); var slide = new SmfSlide { Id = $"slide_{slideNum}" }; var bg = root.Element(enNs + "Background"); if (bg != null) { var solidFill = bg.Element(enNs + "SolidFill"); if (solidFill != null) { slide.Background.Type = SmfFillType.Solid; var color = (string?)solidFill.Element(enNs + "Color"); if (color != null) slide.Background.SolidColor = new SmfColor { Value = color }; } var gradFill = bg.Element(enNs + "GradientFill"); if (gradFill != null) { slide.Background.Type = SmfFillType.Gradient; slide.Background.Gradient = new SmfGradientFill { Type = Enum.TryParse((string?)gradFill.Element(enNs + "Type"), out var gt) ? gt : SmfGradientType.Linear, Angle = (double?)gradFill.Element(enNs + "Angle") ?? 0, }; foreach (var stop in gradFill.Elements(enNs + "Stop")) { slide.Background.Gradient.Stops.Add(new SmfGradientStop { Position = (double?)stop.Element(enNs + "Position") ?? 0, Color = new SmfColor { Value = (string?)stop.Element(enNs + "Color") ?? "000000" }, }); } } var imgFill = bg.Element(enNs + "ImageFill"); if (imgFill != null) { slide.Background.Type = SmfFillType.Image; slide.Background.ImageRef = (string?)imgFill.Element(enNs + "Source"); } } var elementsEl = root.Element(enNs + "Elements"); if (elementsEl != null) { foreach (var elemEl in elementsEl.Elements()) { var elem = ParseElement(elemEl, enNs); if (elem != null) slide.Elements.Add(elem); } } doc.Slides.Add(slide); } private SmfElement? ParseElement(XElement elemEl, XNamespace enNs) { var localName = elemEl.Name.LocalName; var id = (string)elemEl.Attribute("Id") ?? NextId(); switch (localName) { case "TextElement": return ParseTextElement(elemEl, enNs, id); case "ImageElement": return ParseImageElement(elemEl, enNs, id); case "ShapeElement": return ParseShapeElement(elemEl, enNs, id); case "TableElement": return ParseTableElement(elemEl, enNs, id); case "InkElement": return ParseInkElement(elemEl, enNs, id); default: return null; } } private SmfTextElement ParseTextElement(XElement elem, XNamespace enNs, string id) { var te = new SmfTextElement { Id = id }; te.Bounds = ParseBounds(elem, enNs); te.Rotation = (double?)elem.Element(enNs + "Rotation") ?? 0; te.Locked = (bool?)elem.Element(enNs + "Locked") ?? false; var rt = elem.Element(enNs + "RichText"); if (rt != null) { te.RichText.LeftMargin = (double?)rt.Element(enNs + "LeftMargin") ?? 91440; te.RichText.RightMargin = (double?)rt.Element(enNs + "RightMargin") ?? 91440; te.RichText.TopMargin = (double?)rt.Element(enNs + "TopMargin") ?? 91440; te.RichText.BottomMargin = (double?)rt.Element(enNs + "BottomMargin") ?? 91440; te.RichText.WordWrap = (bool?)rt.Element(enNs + "WordWrap") ?? true; var vAlign = (string?)rt.Element(enNs + "VerticalAlignment"); if (vAlign != null && Enum.TryParse(vAlign, out var va)) te.RichText.VerticalAlignment = va; foreach (var para in rt.Elements(enNs + "Paragraph")) { var p = new SmfParagraph(); var algn = (string?)para.Element(enNs + "Alignment"); if (algn != null && Enum.TryParse(algn, out var ha)) p.Alignment = ha; p.SpaceBefore = (double?)para.Element(enNs + "SpaceBefore"); p.SpaceAfter = (double?)para.Element(enNs + "SpaceAfter"); p.LineSpacing = (double?)para.Element(enNs + "LineSpacing"); foreach (var run in para.Elements(enNs + "Run")) { var r = new SmfRun { Text = (string?)run.Element(enNs + "Text") ?? "", }; var fmt = run.Element(enNs + "Formatting"); if (fmt != null) { r.Formatting = new SmfFormatting { Size = (double?)fmt.Element(enNs + "Size"), Bold = (bool?)fmt.Element(enNs + "Bold"), Italic = (bool?)fmt.Element(enNs + "Italic"), Underline = (bool?)fmt.Element(enNs + "Underline"), Strikethrough = (bool?)fmt.Element(enNs + "Strikethrough"), Color = ParseSmfColor(fmt.Element(enNs + "Color")), Font = (string?)fmt.Element(enNs + "Font"), }; } p.Runs.Add(r); } te.RichText.Paragraphs.Add(p); } } return te; } private SmfImageElement ParseImageElement(XElement elem, XNamespace enNs, string id) { var ie = new SmfImageElement { Id = id }; ie.Bounds = ParseBounds(elem, enNs); ie.Rotation = (double?)elem.Element(enNs + "Rotation") ?? 0; ie.Source = (string?)elem.Element(enNs + "Source") ?? ""; var crop = elem.Element(enNs + "Crop"); if (crop != null) { ie.Crop = new SmfCropInfo { Left = (double?)crop.Element(enNs + "Left") ?? 0, Right = (double?)crop.Element(enNs + "Right") ?? 0, Top = (double?)crop.Element(enNs + "Top") ?? 0, Bottom = (double?)crop.Element(enNs + "Bottom") ?? 0, }; } return ie; } private SmfShapeElement ParseShapeElement(XElement elem, XNamespace enNs, string id) { var se = new SmfShapeElement { Id = id }; se.Bounds = ParseBounds(elem, enNs); se.Rotation = (double?)elem.Element(enNs + "Rotation") ?? 0; se.Locked = (bool?)elem.Element(enNs + "Locked") ?? false; var shapeType = (string?)elem.Element(enNs + "ShapeType"); if (shapeType != null && Enum.TryParse(shapeType, out var st)) se.ShapeType = st; var fill = elem.Element(enNs + "Fill"); if (fill != null) { var fillType = (string?)fill.Element(enNs + "Type"); if (fillType == "Solid") { se.Fill.Type = SmfFillType.Solid; var color = (string?)fill.Element(enNs + "Color"); if (color != null) se.Fill.SolidColor = new SmfColor { Value = color }; } } var stroke = elem.Element(enNs + "Stroke"); if (stroke != null) { se.Stroke = new SmfStroke { Color = ParseSmfColor(stroke.Element(enNs + "Color")), Width = (double?)stroke.Element(enNs + "Width") ?? 1, }; var dash = (string?)stroke.Element(enNs + "DashStyle"); if (dash != null && Enum.TryParse(dash, out var ds)) se.Stroke.DashStyle = ds; } var textContent = elem.Element(enNs + "TextContent"); if (textContent != null) { var rt = new SmfRichText(); foreach (var para in textContent.Elements(enNs + "Paragraph")) { var p = new SmfParagraph(); foreach (var run in para.Elements(enNs + "Run")) { p.Runs.Add(new SmfRun { Text = (string?)run.Element(enNs + "Text") ?? "", }); } rt.Paragraphs.Add(p); } se.TextContent = rt; } return se; } private SmfTableElement ParseTableElement(XElement elem, XNamespace enNs, string id) { var te = new SmfTableElement { Id = id }; te.Bounds = ParseBounds(elem, enNs); var cols = elem.Element(enNs + "Columns"); if (cols != null) { foreach (var col in cols.Elements(enNs + "Column")) te.ColumnWidths.Add((double?)col.Attribute("Width") ?? 100000); } var rows = elem.Element(enNs + "Rows"); if (rows != null) { foreach (var row in rows.Elements(enNs + "Row")) { var r = new SmfTableRow { Height = (double?)row.Attribute("Height") ?? 500000, }; foreach (var cell in row.Elements(enNs + "Cell")) { var c = new SmfTableCell { RowSpan = (int?)cell.Attribute("RowSpan") ?? 1, ColSpan = (int?)cell.Attribute("ColSpan") ?? 1, }; var cellRt = cell.Element(enNs + "RichText"); if (cellRt != null) { var ct = new SmfRichText(); foreach (var para in cellRt.Elements(enNs + "Paragraph")) { var p = new SmfParagraph(); foreach (var run in para.Elements(enNs + "Run")) { p.Runs.Add(new SmfRun { Text = (string?)run.Element(enNs + "Text") ?? "", }); } ct.Paragraphs.Add(p); } c.Content = ct; } r.Cells.Add(c); } te.Rows.Add(r); } } return te; } private SmfInkElement ParseInkElement(XElement elem, XNamespace enNs, string id) { var ie = new SmfInkElement { Id = id }; foreach (var stroke in elem.Elements(enNs + "Stroke")) { var s = new SmfInkStroke { Color = new SmfColor { Value = (string?)stroke.Element(enNs + "Color") ?? "000000" }, Width = (double?)stroke.Element(enNs + "Width") ?? 2, IsHighlighter = (bool?)stroke.Element(enNs + "Highlighter") ?? false, Transparency = (double?)stroke.Element(enNs + "Transparency") ?? 0, }; var pts = stroke.Element(enNs + "Points"); if (pts != null) { foreach (var pt in pts.Elements(enNs + "Point")) { s.Points.Add(new SmfInkPoint { X = (double)pt.Attribute("X"), Y = (double)pt.Attribute("Y"), Pressure = (double?)pt.Attribute("P") ?? 0.5, }); } } ie.Strokes.Add(s); } return ie; } private void ParseLegacyElements(XElement slideEl, SmfSlide slide) { var width = (double?)slideEl.Element("Width") ?? 12192000; var height = (double?)slideEl.Element("Height") ?? 6858000; var elements = slideEl.Element("Elements"); if (elements == null) return; foreach (var elem in elements.Elements()) { var localName = elem.Name.LocalName; if (localName == "TextSaveInfo") { var te = new SmfTextElement { Id = (string)elem.Element("Id") ?? NextId(), Bounds = new SmfBounds { X = (double)elem.Element("X"), Y = (double)elem.Element("Y"), Width = (double)elem.Element("Width"), Height = (double)elem.Element("Height"), }, Rotation = (double?)elem.Element("Rotation") ?? 0, Locked = (bool?)elem.Element("Locked") ?? false, }; var richText = elem.Element("RichText"); if (richText != null) { te.RichText.LeftMargin = (double?)richText.Element("LeftMargin") ?? 91440; te.RichText.RightMargin = (double?)richText.Element("RightMargin") ?? 91440; te.RichText.TopMargin = (double?)richText.Element("TopMargin") ?? 91440; te.RichText.BottomMargin = (double?)richText.Element("BottomMargin") ?? 91440; foreach (var para in richText.Elements("Paragraph")) { var p = new SmfParagraph(); foreach (var run in para.Elements("Run")) { var r = new SmfRun { Text = (string?)run.Element("Text") ?? "", }; var fmt = run.Element("Formatting"); if (fmt != null) { r.Formatting = new SmfFormatting { Size = (double?)fmt.Element("Size"), Bold = (bool?)fmt.Element("Bold"), Italic = (bool?)fmt.Element("Italic"), Color = ParseColorAttr(fmt.Element("Color")), Font = (string?)fmt.Element("Font"), }; } p.Runs.Add(r); } te.RichText.Paragraphs.Add(p); } } slide.Elements.Add(te); } } } private SmfBounds ParseBounds(XElement parent, XNamespace enNs) { var b = parent.Element(enNs + "Bounds"); if (b == null) return new SmfBounds(); return new SmfBounds { X = (double?)b.Element(enNs + "X") ?? 0, Y = (double?)b.Element(enNs + "Y") ?? 0, Width = (double?)b.Element(enNs + "Width") ?? 0, Height = (double?)b.Element(enNs + "Height") ?? 0, }; } private SmfColor? ParseSmfColor(XElement? el) { if (el == null) return null; return new SmfColor { Value = el.Value }; } private SmfColor? ParseColorAttr(XElement? el) { if (el == null) return null; return new SmfColor { Value = el.Value }; } private PackagePart? FindSlidePart(Package enbx, string target, int slideNum) { var possiblePaths = new[] { target, $"/{target}", $"/slides/slide{slideNum}.xml", $"slides/slide{slideNum}.xml", }; foreach (var path in possiblePaths) { var uri = PackUriHelper.CreatePartUri(new Uri(path, UriKind.Relative)); if (enbx.PartExists(uri)) return enbx.GetPart(uri); } return null; } private string NextId() => $"elem_{_nextId++}"; } }