// Gets a single timestamp component static int GetTimestampVar(FbxNode timestamp, string element) { var elementNode = timestamp[element]; if (elementNode != null && elementNode.Properties.Count > 0) { var prop = elementNode.Properties[0]; if (prop is int || prop is long) { return((int)prop); } } throw new FbxException(timePath, -1, "Timestamp has no " + element); }
/// <summary> /// Returns all sub-nodes with name /// </summary> /// <returns></returns> private List <FbxNode> GetNodesByValue(FbxNode node, string value, List <FbxNode> c = null) { if (c == null) { c = new List <FbxNode>(); } if (node == null) { return(c); } if (node.Value != null && node.Value.ToString().Equals(value)) { c.Add(node); } foreach (var child in node.Nodes) { GetNodesByValue(child, value, c); } return(c); }
/// <summary> /// Returns all sub-nodes with name /// </summary> /// <returns></returns> private List <FbxNode> GetNodesByName(FbxNode node, string name, List <FbxNode> c = null) { if (c == null) { c = new List <FbxNode>(); } if (node == null) { return(c); } if (node.Name.Equals(name)) { c.Add(node); } foreach (var child in node.Nodes) { GetNodesByName(child, name, c); } return(c); }
/// <summary> /// Reads the next node from the stream /// </summary> /// <returns>The read node, or <c>null</c></returns> public FbxNode ReadNode() { var first = ReadToken(); var id = first as Identifier; if (id == null) { if (first is EndOfStream) { return(null); } throw new FbxException(line, column, "Unexpected '" + first + "', expected an identifier"); } var node = new FbxNode { Name = id.String }; // Read properties object token; bool expectComma = false; while (!'{'.Equals(token = ReadToken()) && !(token is Identifier) && !'}'.Equals(token)) { if (expectComma) { if (!','.Equals(token)) { throw new FbxException(line, column, "Unexpected '" + token + "', expected a ','"); } expectComma = false; continue; } if (token is char) { var c = (char)token; switch (c) { case '*': token = ReadArray(); break; case '}': case ':': case ',': throw new FbxException(line, column, "Unexpected '" + c + "' in property list"); } } node.Properties.Add(token); expectComma = true; // The final comma before the open brace isn't required } // TODO: Merge property list into an array as necessary // Now we're either at an open brace, close brace or a new node if (token is Identifier || '}'.Equals(token)) { prevToken = token; return(node); } // The while loop can't end unless we're at an open brace, so we can continue right on object endBrace; while (!'}'.Equals(endBrace = ReadToken())) { prevToken = endBrace; // If it's not an end brace, the next node will need it node.Nodes.Add(ReadNode()); } if (node.Nodes.Count < 1) // If there's an open brace, we want that to be preserved { node.Nodes.Add(null); } return(node); }
// Adds the given node text to the string void BuildString(FbxNode node, StringBuilder sb, bool writeArrayLength, int indentLevel = 0) { nodePath.Push(node.Name ?? ""); int lineStart = sb.Length; // Write identifier for (int i = 0; i < indentLevel; i++) { sb.Append('\t'); } sb.Append(node.Name).Append(':'); // Write properties var first = true; for (int j = 0; j < node.Properties.Count; j++) { var p = node.Properties[j]; if (p == null) { continue; } if (!first) { sb.Append(','); } sb.Append(' '); if (p is string) { sb.Append('"').Append(p).Append('"'); } else if (p is Array) { var array = (Array)p; var elementType = p.GetType().GetElementType(); // ReSharper disable once PossibleNullReferenceException // We know it's an array, so we don't need to check for null if (array.Rank != 1 || !elementType.IsPrimitive) { throw new FbxException(nodePath, j, "Invalid array type " + p.GetType()); } if (writeArrayLength) { sb.Append('*').Append(array.Length).Append(" {\n"); lineStart = sb.Length; for (int i = -1; i < indentLevel; i++) { sb.Append('\t'); } sb.Append("a: "); } bool pFirst = true; foreach (var v in (Array)p) { if (!pFirst) { sb.Append(','); } var vstr = v.ToString(); if ((sb.Length - lineStart) + vstr.Length >= MaxLineLength) { sb.Append('\n'); lineStart = sb.Length; } sb.Append(vstr); pFirst = false; } if (writeArrayLength) { sb.Append('\n'); for (int i = 0; i < indentLevel; i++) { sb.Append('\t'); } sb.Append('}'); } } else if (p is char) { sb.Append((char)p); } else if (p.GetType().IsPrimitive&& p is IFormattable) { sb.Append(p); } else { throw new FbxException(nodePath, j, "Invalid property type " + p.GetType()); } first = false; } // Write child nodes if (node.Nodes.Count > 0) { sb.Append(" {\n"); foreach (var n in node.Nodes) { if (n == null) { continue; } BuildString(n, sb, writeArrayLength, indentLevel + 1); } for (int i = 0; i < indentLevel; i++) { sb.Append('\t'); } sb.Append('}'); } sb.Append('\n'); nodePath.Pop(); }
// Writes a single document to the buffer void WriteNode(FbxNode node) { if (node == null) { stream.BaseStream.Write(nullData, 0, nullData.Length); } else { nodePath.Push(node.Name ?? ""); var name = string.IsNullOrEmpty(node.Name) ? null : Encoding.ASCII.GetBytes(node.Name); if (name != null && name.Length > byte.MaxValue) { throw new FbxException(stream.BaseStream.Position, "Node name is too long"); } // Header var endOffsetPos = stream.BaseStream.Position; stream.Write(0); // End offset placeholder stream.Write(node.Properties.Count); var propertyLengthPos = stream.BaseStream.Position; stream.Write(0); // Property length placeholder stream.Write((byte)(name?.Length ?? 0)); if (name != null) { stream.Write(name); } // Write properties and length var propertyBegin = stream.BaseStream.Position; for (int i = 0; i < node.Properties.Count; i++) { WriteProperty(node.Properties[i], i); } var propertyEnd = stream.BaseStream.Position; stream.BaseStream.Position = propertyLengthPos; stream.Write((int)(propertyEnd - propertyBegin)); stream.BaseStream.Position = propertyEnd; // Write child nodes if (node.Nodes.Count > 0) { foreach (var n in node.Nodes) { if (n == null) { continue; } WriteNode(n); } WriteNode(null); } // Write end offset var dataEnd = stream.BaseStream.Position; stream.BaseStream.Position = endOffsetPos; stream.Write((int)dataEnd); stream.BaseStream.Position = dataEnd; nodePath.Pop(); } }
/// <summary> /// Reads a single node. /// </summary> /// <remarks> /// This won't read the file header or footer, and as such will fail if the stream is a full FBX file /// </remarks> /// <returns>The node</returns> /// <exception cref="FbxException">The FBX data was malformed /// for the reader's error level</exception> public FbxNode ReadNode(int version) { var endOffset = (version > 7400) ? stream.ReadInt64() : stream.ReadInt32(); var numProperties = (version > 7400) ? stream.ReadInt64() : stream.ReadInt32(); var propertyListLen = (version > 7400) ? stream.ReadInt64() : stream.ReadInt32(); var nameLen = stream.ReadByte(); var name = nameLen == 0 ? "" : Encoding.ASCII.GetString(stream.ReadBytes(nameLen)); if (endOffset == 0) { // The end offset should only be 0 in a null node if (errorLevel >= ErrorLevel.Checked && (numProperties != 0 || propertyListLen != 0 || !string.IsNullOrEmpty(name))) { throw new FbxException(stream.BaseStream.Position, "Invalid node; expected NULL record"); } return(null); } var node = new FbxNode { Name = name }; var propertyEnd = stream.BaseStream.Position + propertyListLen; // Read properties for (int i = 0; i < numProperties; i++) { node.Properties.Add(ReadProperty()); } if (errorLevel >= ErrorLevel.Checked && stream.BaseStream.Position != propertyEnd) { throw new FbxException(stream.BaseStream.Position, "Too many bytes in property list, end point is " + propertyEnd); } // Read nested nodes var listLen = endOffset - stream.BaseStream.Position; if (errorLevel >= ErrorLevel.Checked && listLen < 0) { throw new FbxException(stream.BaseStream.Position, "Node has invalid end point"); } if (listLen > 0) { FbxNode nested; do { nested = ReadNode(version); node.Nodes.Add(nested); } while (nested != null); if (errorLevel >= ErrorLevel.Checked && stream.BaseStream.Position != endOffset) { throw new FbxException(stream.BaseStream.Position, "Too many bytes in node, end point is " + endOffset); } } return(node); }