Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
// using Newtonsoft.Json;
using System.Text.Json;
using SharpGLTF.Schema2;
using BYTES = System.ArraySegment<byte>;
using SCHEMA2 = SharpGLTF.Schema2.ModelRoot;
using VALIDATIONMODE = SharpGLTF.Validation.ValidationMode;
namespace SharpGLTF.IO
{
/// <summary>
/// Callback used for loading associated files of current model.
/// </summary>
/// <param name="assetName">the asset relative path.</param>
/// <returns>The file contents as a <see cref="byte"/> array.</returns>
public delegate BYTES FileReaderCallback(String assetName);
public delegate String UriResolver(String relativeUri);
/// <summary>
/// Context for reading a <see cref="SCHEMA2"/>.
/// </summary>
public class ReadContext : Schema2.ReadSettings
{
#region lifecycle
public static ReadContext Create(FileReaderCallback callback)
{
Guard.NotNull(callback, nameof(callback));
return new ReadContext(callback);
}
public static ReadContext CreateFromFile(string filePath)
{
Guard.FilePathMustExist(filePath, nameof(filePath));
var dir = Path.GetDirectoryName(filePath);
return CreateFromDirectory(dir);
}
public static ReadContext CreateFromDirectory(string directoryPath)
{
directoryPath = System.IO.Path.GetFullPath(directoryPath);
string _uriSolver(string rawUri)
{
var path = Uri.UnescapeDataString(rawUri);
return Path.Combine(directoryPath, path);
}
BYTES _loadFile(string rawUri)
{
var path = _uriSolver(rawUri);
var content = File.ReadAllBytes(path);
return new BYTES(content);
}
return new ReadContext(_loadFile, _uriSolver);
}
public static ReadContext CreateFromDictionary(IReadOnlyDictionary<string, BYTES> dictionary, bool checkExtensions = true)
{
return new ReadContext(rawUri => dictionary[rawUri], null, checkExtensions);
}
private ReadContext(FileReaderCallback reader, UriResolver uriResolver = null, bool checkExtensions = true)
{
_FileReader = reader;
_UriResolver = uriResolver;
_CheckSupportedExtensions = checkExtensions;
}
internal ReadContext(ReadContext other)
: base(other)
{
this._FileReader = other._FileReader;
this.ImageDecoder = other.ImageDecoder;
}
#endregion
#region data
private FileReaderCallback _FileReader;
private UriResolver _UriResolver;
/// <summary>
/// When loading a GLB, this represents the internal binary data chunk.
/// </summary>
private Byte[] _BinaryChunk;
/// <summary>
/// Gets a value indicating whether to check used/required extensions.
/// </summary>
internal Boolean _CheckSupportedExtensions { get; private set; } = true;
#endregion
#region API - File System
public bool TryGetFullPath(string relativeUri, out string fullPath)
{
if (_UriResolver == null) { fullPath = null; return false; }
fullPath = _UriResolver(relativeUri);
return true;
}
public BYTES ReadAllBytesToEnd(string fileName)
{
if (_BinaryChunk != null)
{
if (string.IsNullOrEmpty(fileName)) return new BYTES(_BinaryChunk);
}
return _FileReader(fileName);
}
/// <summary>
/// Opens a file relative to this <see cref="ReadContext"/>.
/// </summary>
/// <param name="fileName">A relative file Name path.</param>
/// <returns>A <see cref="Stream"/>.</returns>
public Stream OpenFile(string fileName)
{
var content = _FileReader(fileName);
return new MemoryStream(content.Array, content.Offset, content.Count);
}
#endregion
#region API
public Validation.ValidationResult Validate(string filePath)
{
using (var stream = File.OpenRead(filePath))
{
bool isBinary = BinarySerialization._Identify(stream);
if (isBinary) return _ReadGLB(stream).Validation;
var json = stream.ReadBytesToEnd();
return _Read(json).Validation;
}
}
/// <summary>
/// Reads a <see cref="SCHEMA2"/> instance from a <see cref="Stream"/> containing a GLB or a GLTF file.
/// </summary>
/// <param name="stream">A <see cref="Stream"/> to read from.</param>
/// <returns>A <see cref="SCHEMA2"/> instance.</returns>
public SCHEMA2 ReadSchema2(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
Guard.IsTrue(stream.CanRead, nameof(stream));
bool binaryFile = BinarySerialization._Identify(stream);
return binaryFile ? ReadBinarySchema2(stream) : ReadTextSchema2(stream);
}
internal SCHEMA2 _ReadFromDictionary(string fileName)
{
var json = this.ReadAllBytesToEnd(fileName);
var mv = this._Read(json);
if (mv.Validation.HasErrors) throw mv.Validation.Errors.FirstOrDefault();
return mv.Model;
}
/// <summary>
/// Reads a <see cref="SCHEMA2"/> instance from a <see cref="Stream"/> containing a GLTF file.
/// </summary>
/// <param name="stream">A <see cref="Stream"/> to read from.</param>
/// <returns>A <see cref="SCHEMA2"/> instance.</returns>
public SCHEMA2 ReadTextSchema2(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
Guard.IsTrue(stream.CanRead, nameof(stream));
var json = stream.ReadBytesToEnd();
var mv = this._Read(json);
if (mv.Validation.HasErrors) throw mv.Validation.Errors.FirstOrDefault();
return mv.Model;
}
/// <summary>
/// Reads a <see cref="SCHEMA2"/> instance from a <see cref="Stream"/> containing a GLB file.
/// </summary>
/// <param name="stream">A <see cref="Stream"/> to read from.</param>
/// <returns>A <see cref="SCHEMA2"/> instance.</returns>
public SCHEMA2 ReadBinarySchema2(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
Guard.IsTrue(stream.CanRead, nameof(stream));
var mv = this._ReadGLB(stream);
if (mv.Validation.HasErrors) throw mv.Validation.Errors.FirstOrDefault();
return mv.Model;
}
#endregion
#region core
private (SCHEMA2 Model, Validation.ValidationResult Validation) _ReadGLB(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
IReadOnlyDictionary<uint, byte[]> chunks;
try
{
chunks = BinarySerialization.ReadBinaryFile(stream);
}
catch (System.IO.EndOfStreamException ex)
{
var vr = new Validation.ValidationResult(null, this.Validation);
vr.SetError(new Validation.SchemaException(null, ex.Message));
return (null, vr);
}
catch (Validation.SchemaException ex)
{
var vr = new Validation.ValidationResult(null, this.Validation);
vr.SetError(ex);
return (null, vr);
}
var context = this;
if (chunks.ContainsKey(BinarySerialization.CHUNKBIN))
{
// clone self
var binChunk = chunks[BinarySerialization.CHUNKBIN];
context = new ReadContext(context);
context._BinaryChunk = binChunk;
}
var jsonChunk = chunks[BinarySerialization.CHUNKJSON];
return context._Read(jsonChunk);
}
private (SCHEMA2 Model, Validation.ValidationResult Validation) _Read(ReadOnlyMemory<Byte> jsonUtf8Bytes)
{
var root = new SCHEMA2();
var vcontext = new Validation.ValidationResult(root, this.Validation);
#if !SUPRESSTRYCATCH
try {
#endif
if (jsonUtf8Bytes.IsEmpty) throw new System.Text.Json.JsonException("JSon is empty.");
var reader = new Utf8JsonReader(jsonUtf8Bytes.Span);
if (!reader.Read())
{
vcontext.SetError(new Validation.SchemaException(root, "Json is empty"));
return (null, vcontext);
}
root.Deserialize(ref reader);
root.OnDeserializationCompleted();
// binary chunk check
foreach (var b in root.LogicalBuffers) b.OnValidateBinaryChunk(vcontext.GetContext(), this._BinaryChunk);
// schema validation
if (this._CheckSupportedExtensions)
{
root._ValidateExtensions(vcontext.GetContext());
var ex = vcontext.Errors.FirstOrDefault();
if (ex != null) return (null, vcontext);
}
// we must do a basic validation before resolving external dependencies
if (true)
{
root.ValidateReferences(vcontext.GetContext());
var ex = vcontext.Errors.FirstOrDefault();
if (ex != null) return (null, vcontext);
}
// resolve external dependencies
root._ResolveSatelliteDependencies(this);
// full validation
if (this.Validation != VALIDATIONMODE.Skip)
{
root.ValidateContent(vcontext.GetContext());
var ex = vcontext.Errors.FirstOrDefault();
if (ex != null) return (null, vcontext);
}
#if !SUPRESSTRYCATCH
}
catch (JsonException rex)
{
vcontext.SetError(new Validation.SchemaException(root, rex));
return (null, vcontext);
}
catch (System.FormatException fex)
{
vcontext.SetError(new Validation.ModelException(null, fex));
return (null, vcontext);
}
catch (ArgumentException aex)
{
vcontext.SetError(new Validation.ModelException(root, aex));
return (null, vcontext);
}
catch (Validation.ModelException mex)
{
vcontext.SetError(mex);
return (null, vcontext);
}
#endif
return (root, vcontext);
}
#endregion
#region extras
/// <summary>
/// Tries to identify a stream as a text (glTF) or binary (GLB) format.
/// </summary>
/// <param name="stream">An open <see cref="Stream"/> where <see cref="Stream.CanSeek"/> must be true.</param>
/// <returns>True if it's a binary GLB format.</returns>
/// <remarks>
/// After identification, the <see cref="Stream.Position"/> will be reset to the position it had before calling this method.
/// </remarks>
public static bool IdentifyBinaryContainer(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
return BinarySerialization._Identify(stream);
}
public static String ReadJson(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
bool binaryFile = IdentifyBinaryContainer(stream);
if (binaryFile)
{
var chunks = BinarySerialization.ReadBinaryFile(stream);
return Encoding.UTF8.GetString(chunks[BinarySerialization.CHUNKJSON]);
}
using (var streamReader = new StreamReader(stream))
{
return streamReader.ReadToEnd();
}
}
public static ReadOnlyMemory<Byte> ReadJsonBytes(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
bool binaryFile = IdentifyBinaryContainer(stream);
if (binaryFile)
{
var chunks = BinarySerialization.ReadBinaryFile(stream);
return chunks[BinarySerialization.CHUNKJSON];
}
return stream.ReadBytesToEnd();
}
#endregion
}
}
d49ffeb42This file is pinned to the selected release. Download the original to open it in your design tool.