575 lines
23 KiB
C#
575 lines
23 KiB
C#
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++}";
|
|
}
|
|
}
|