Files
Better-Seewo/Better-EN5/Services/Conversion/EnbxReader.cs

575 lines
23 KiB
C#
Raw Normal View History

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 static readonly XNamespace Rel = "http://schemas.openxmlformats.org/package/2006/relationships";
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.Root ?? throw new InvalidOperationException("Missing document root"), 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(Rel + "Relationship") ?? Enumerable.Empty<XElement>())
{
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;
var enNs = root.Name.Namespace;
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 ?? throw new InvalidOperationException("Missing slide root");
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<SmfGradientType>((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<SmfVerticalAlignment>(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<SmfHorizontalAlignment>(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<SmfShapeType>(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<SmfStrokeDashStyle>(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++}";
}
}