Sample { array of ints = 1 2 3 4 MATRIX matrix of ints = (1 2 3 1 2 3) some string = blah blah blah sample subtree { #\ a comment element { name = foo } #\ another comment element { name = bar } } a double = 0.3 #\ with a comment } ------- Building the tree ------------------------- uses QUB_Node; ... var tree, subtree, child :INode; i, j :Integer; // creating the root node: tree := TNode.Create('Sample'); // accessing a child node by name -- it will be created and appended if it doesn't exist child := tree['array of ints']; // putting data in the child node child.setupNumData( NODE_DATATYPE_INT, 4, 1 ); for i := 0 to child.DataCount - 1 do begin child.Data[i] := i; end; // adding a node with matrix data (reusing the INode variable) child := tree['matrix of ints']; child.setupNumData( NODE_DATATYPE_INT, 2, 3 ); // filling the data for i := 0 to child.Rows - 1 do begin for j := 0 to child.Cols - 1 do begin child.MatrixData[i, j] := j; end; end; // adding a node with string data tree['some string'].SetData( 'blah blah blah' ); // adding a subtree subtree := tree['sample subtree']; child := subtree.AppendChild( 'element' ); child['name'].SetData( 'foo' ); // cloning an "element" and inserting it after the original // note that inserting is more efficient than appending child = subtree.InsertClone( child, child, True ); child["name"].SetData( 'bar' ); // inserting a comment line (which is represented as a node) // as the first child subtree.InsertChild( TNode.CreateComment( ' a comment' ) ); // and after the first "element" subtree.InsertChild( subtree['element'], TNode.CreateComment( ' another comment' ) ); // adding a node with one double as data child = tree['a double']; child.SetData( 0.3 ); child.LineComment := ' with a comment'; -------- Saving and reopening --------------------------------- // saving in binary -- the file remains open until you close it if not tree.SaveAs('sample.qtr') then raise Exception.Create('can''t save'); // saving a copy as text -- the binary file remains open if not tree.SaveAsText('sample.qtt') then raise Exception.Create('can''t save as text'); // do some modifications here ... tree.InsertChild('abc').SetData( 123 ); // saving changes to disk tree.Save; // closing the binary file tree.Close; 1 // reading the text file tree = TNode.CreateFromText('sample.qtt'); // opening the binary file for reading and writing tree = TNode.Open('sample.qtr'); if tree.IsNull then raise Exception.Create('can''t open'); -------- Reading and modifying data --------------------------------- var someString :String; aDouble :Double; iarr :TPAInteger; // reading some data someString := tree['some string'].DataAsString; aDouble := Double(tree['a double'].Data[0]); // raw array access child := tree['array of ints']; iarr := TPAInteger( child.DataPtr ); for i := 0 to child.DataCount - 1 do begin someString := Format( '%d', [ iarr[i] ] ); end; // read/write a row of data from a buffer child := tree['matrix of ints']; iarr := IAlloc1( child.Cols ); child.ReadRows( iarr, QUB_FirstLast(0, 0) ); child.WriteRows( iarr, QUB_FirstLast(1, 1) ); // replacing data child := tree['pqz']; child.SetData( 'foo' ); child.SetupNumData( NODE_DATATYPE_DOUBLE, 3, 2 ); // resizing the number of rows (so it becomes 4x2) child.ResizeData( 4 ); // removing data child.ClearData; -------- Working with large data -------------------------------------- In this section we create a QDF file, but never is the entire data in memory at once. var qdf, segment, channels :INode; mplexData :TPASmallInt; firstRow, i :Integer; qdf := TNode.Create('DataFile'); if qdf.SaveAs('sample.qdf') then begin // note that saveAs is the first step; // this establishes storage so we can selectively load data // set up structure and metadata segment := qdf['Segments']['Segment']; segment['StartTime'].SetData( 0.0 ); channels := segment['Channels']; qdf['Sampling'].SetData( 5.0e-5 ); qdf['Scaling'].SetData( 320.0 ); qdf['ADChannelCount'].SetData( 2 ); qdf['ADDataSize'].SetData( 2 ); qdf['ADDataType'].SetData( Integer(NODE_DATATYPE_SMALLINT) ); // fill channels with 2 channels of 5 megapoints each; // each row is a sample; each column is a channel; // set PRELOAD to false first, so no RAM is allocated. channels.Preload := False; channels.SetupNumData( NODE_DATATYPE_SMALLINT, 5000000, 2 ); // in a fit of lameness, just set the data points to zero, 100000 samples at a time firstRow := 0; while firstRow < 5000000 do begin channels.LoadRows( firstRow, firstRow + 100000 - 1, False ); // false: don't bother reading the (uninitialized) contents mplexData := TPASmallInt( channels.DataPtr ); for i := 0 to 200000 - 1 do begin mplexData[i] := 0; end; firstRow := firstRow + 100000; end; channels.UnloadRows( True ); qdf.Save; end else begin raise Exception.Create('can''t save sample.qdf'); end; ----------- Working with child nodes ---------------------------------- // tree.Find(name) vs. tree[name] tree[name] looks for the first child with that name; if none, it creates one automatically. tree.Find(name) acts identically if such a child exists, but if none, it returns a null node. // seeing if tree has a child named 'qaz' if tree.Find('qaz').IsNull then begin print "there is no child named 'qaz'" end; // printing the name of each child node: child := tree.Child; while not child.IsNull do begin print child.Name; child := child.Sibling; end; // printing the "name" of each "element" in "sample subtree" child := tree['sample subtree'].Find('element'); while not child.IsNull do begin print child['name'].DataAsString; child := child.Sibling('element'); end; ----------------------------------------------------------------------- // Making a copy of the whole tree treecopy = tree.Clone(True); // or just the node and its data treecopy = tree.Clone(false); ----------- Working with TQUB_QFS_PNodeMem (aka QTR_Impl*) ----------- INode acts as a wrapper around TQUB_QFS_PNodeMem. INode is more convenient and less error prone, but sometimes you need to pass or store qub trees as pointers. // a function of a tree, returning a tree function func( treeptr :TQUB_QFS_PNodeMem ): TQUB_QFS_PNodeMem; var tree, clone :INode; begin // wrap treeptr in TNode for easy manipulation tree := TNode.CreateUsing( treeptr ); clone := tree.Clone(True); // get a pointer for the clone, and incref it so it outlasts the INode variable Result := clone.GetNodeMem; Node_INCREF( Result ); end; // calling func tree := TNode.Create('tree'); resultPtr := func( tree.GetNodeMem ); resultTree := TNode.CreateUsing( resultPtr ); Node_DECREF( resultPtr ); In general, you should pass a "borrowed reference", and return a "new reference". On receiving the new reference, wrap it in a TNode, then decref to release the new ref. // storing a qub tree in a TList var List :TList; begin List := TList.Create; List.Add( tree.GetNodeMem ); Node_INCREF( tree.GetNodeMem ); // using a node from List tree2 := TNode.CreateUsing( TQUB_QFS_PNodeMem(List[0]) ); ... // removing it from the list Node_DECREF( TQUB_QFS_PNodeMem(List[0]) ); List.Delete( 0 ); end;
1 Recall that nodes are reference-counted: they are released when the last reference expires. The flip side is that nodes stay valid as long as they are referenced, even if the file has been closed. Not a big deal when all the data is in memory anyway, but for files with large partially-loaded data you could end up with the whole thing in RAM by mistake. Unfortunately, the variable 'tree :INode' itself counts as a reference to the underlying root node, so calling tree.Close guarantees the whole thing will stay in memory. To avoid this problem, don't call Close. Files are closed automatically when no variables reference them, so either let the variables go out of scope, or assign nil: child = nil; subtree = nil; tree = nil; Notice that the root was the last to go. Otherwise subtree would still have been referenced when the file closed, and would have jumped into RAM in order to stay valid.