Question

I'm using jsTree to view hierarchical data that is stored in a mySQL database as a nested set (left, right, level, etc.). This is working fine, but I need to allow users to import data by uploading a CSV file. When they do so, any existing data in the table will be removed so I don't have to worry about updating the left/right fields.

The data they will be uploading will be in this format:

"Code","Title"
"100","Unit 100"
"200","Unit 200"
"101","Task 101: This is a task"
"102","Task 102: Another task"
"201","Task 201: Yet another"   
"300","Unit 300"
"301","Task 301: Another one"

Everything will be a child of a main "Group" that is a level 1 node. All of the "codes" divisible by 100 (ie. 100, 200, 300) will be level 2 (parent nodes.. children of "Group"). All others will be level 3 (child) nodes of their respective parent nodes (ie. 101 and 102 are children of 100, 201 is a child of 200, etc.)

The resulting table in mySQL should look like this:

id  parent_id   position    left    right   level   title
1   0           0           1       18      0       ROOT     
2   1           0           2       17      1       Group
3   2           0           3       8       2       Unit 100
4   2           1           9       12      2       Unit 200
5   3           0           4       5       3       Task 101: This is a task
6   3           1           6       7       3       Task 102: Another task
7   4           0           10      11      3       Task 201: Yet another   
8   2           2           13      16      2       Unit 300
9   8           0           14      15      3       Task 301: Another one

The tree would then look like this:

tree

My question is: using PHP, what is the best method to accomplish this? I already have code in place that pulls the data contained in the uploaded CSV file and stores it in an array, but I'm not sure what the logic to convert this to a nested set should look like.

Right now, the data is stored in a 2-dimensional array called $data (in the format $data[$col][$row]):

$data[0][0] = "Code";
$data[0][1] = "100";
$data[0][2] = "200";
$data[0][3] = "101";
$data[0][4] = "102";
$data[0][5] = "201";
$data[0][6] = "300";
$data[0][7] = "301";
$data[1][0] = "Title";
$data[1][1] = "Unit 100";
$data[1][2] = "Unit 200";
$data[1][3] = "Task 101: This is a task";
$data[1][4] = "Task 102: Another task";
$data[1][5] = "Task 201: Yet another";
$data[1][6] = "Unit 300";
$data[1][7] = "Task 301: Another one";

Array ( [0] => Array ( [0] => Code [1] => 100 [2] => 200 [3] => 101 [4] => 102 [5] => 201 [6] => 300 [7] => 301 ) [1] => Array ( [0] => Title [1] => Unit 100 [2] => Unit 200 [3] => Task 101: This is a task [4] => Task 102: Another task [5] => Task 201: Yet another [6] => Unit 300 [7] => Task 301: Another one ) )

Any help would be very much appreciated. I now have the parent_id, position, and level being calculated correctly... I just need to figure out the left/right part. Here is the code I'm currently using (thanks for getting me started Matteo):

$rows = array();

// insert ROOT row
$rows[] = array(
    'id' => 1,
    'parent_id' => 0,
    'position' => 0,
    'left' => 1,
    'right' => 10000,       // just a guess, will need updated later
    'level' => 0,
    'title' => 'ROOT',
);

echo "<br>";
print_r($rows[0]);

// insert group row
$rows[] = array(
    'id' => 2,
    'parent_id' => 1,
    'position' => 0,
    'left' => 2,
    'right' => 9999,        // just a guess, will need updated later
    'level' => 1,
    'title' => 'Group',
);

echo "<br>";
print_r($rows[1]);

// next ID to be used
$id = 3;

// keep track of code => ID correspondence
$map = array();

// parse data
for ($i = 1, $c = count($data[0]); $i < $c; ++$i) {
    // save ID in the map
    $map[$data[0][$i]] = $id;

    // initialize the current row
    $row = array(
        'id' => $id,
        'parent_id' => 1,           
        'position' => 0,            
        'left' => 0,
        'right' => 0,           
        'level' => 1,               
        'title' => $data[1][$i],
    );

    // if the code is multiple of 100
    if ($data[0][$i] % 100 == 0) {
        $row['parent_id'] = 2;
        $row['level'] = 2;
        $row['position'] = (floor($data[0][$i] / 100)) - 1;
    } else {
        // get parent id from map
        $row['parent_id'] = $map[floor($data[0][$i] / 100) * 100];
        $row['level'] = 3;
        $row['position'] = $data[0][$i] % 100;
    }

    // add the row
    $rows[] = $row;

    ++$id;

    echo "<br>";
    print_r($row);
}
Était-ce utile?

La solution

Given your $data array, you could parse it like this:

// this will contain all the rows to be inserted in your DB
$rows = array();

// insert ROOT row
$rows[0] = array(
    'id' => 1,
    'parent_id' => 0,
    'position' => 0,
    'level' => 0,
    'left' => 1,
    'right' => 10000,
    'title' => 'ROOT',
);

// insert group row
$rows[1] = array(
    'id' => 2,
    'parent_id' => 1,
    'position' => 0,
    'level' => 1,
    'left' => 2,
    'right' => 9999,
    'title' => 'Group',
);

// keep trace of code => ID correspondence
$map = array();

// next ID to be used
$id = 3;

// keep father => sons relationship
$tree = array();

// keep trace of code => row index correspondence
$indexes = array();

// next row index
$index = 2;

// parse your data
for ($i = 1, $c = count($data[0]); $i < $c; ++$i) {
    // current code
    $code = $data[0][$i];

    // save ID in the map
    $map[$code] = $id;

    // update the indexes map
    $indexes[$code] = $index;

    // prepare the current row
    $row = array(
        'id' => $id,
        'title' => $data[1][$i],
    )

    // get the value of code mod 100
    $mod = $code % 100;

    // if the code is multiple of 100
    if ($mod == 0) {
        // the parent_id is 2
        $row['parent_id'] = 2;

        // it is level two
        $row['level'] = 2;

        // compute position
        $row['position'] = floor($code / 100) - 1;
    }
    else {
        // get the parent code
        $parent = floor($code / 100) * 100;

        // get parent id from map using parent code
        $row['parent_id'] = $map[$parent];

        // it is level three
        $row['level'] = 3;

        // save position
        $row['position'] = $mod;

        // save in relationship tree
        $tree[$parent][] = $code;
    }

    // add the row
    $rows[$index] = $row;

    // prepare next id
    ++$id;

    // update row index
    ++$index;
}

// sort the relationship tree base on the parent code (key)
ksort($tree, SORT_NUMERIC);

// next left value
$left = 3;

// now, using the relationship tree, assign left and right
foreach ($tree as $parent => $sons) {
    // calculate parent left value
    $parentLeft = $left;

    // prepare next left value
    ++$left;

    // to be sure that the sons are in order
    sort($sons, SORT_NUMERIC);

    // assign values to sons
    foreach ($sons as $son) {
        // index in the rows array
        $index = $indexes[$son];

        // set left value
        $rows[$index]['left'] = $left;

        // set right value
        $rows[$index]['right'] = $left + 1;

        // increment left value
        $left += 2;
    }

    // calculate parent right value
    $parentRight = $left;

    // prepare next left value
    ++$left;

    // index of parent in the rows array
    $index = $indexes[$parent];

    // set the values
    $rows[$index]['left'] = $parentLeft;
    $rows[$index]['right'] = $parentRight;
}

// update group row right value
$rows[1]['right'] = $left;

// prepare next left value
++$left;

// update root row right value
$rows[0]['right'] = $left;

At this point, you can insert all the rows one at a time.

EDIT: now the script should handle all the required values correctly.

Autres conseils

I would use Doctrine2 with a Nested Set Extension. You could use a nice and convenient API and don't have to worry about the nested set implementation:

See http://www.gediminasm.org/article/tree-nestedset-behavior-extension-for-doctrine-2 or http://wildlyinaccurate.com/simple-nested-sets-in-doctrine-2

There are several extensions on github. Actually, i don't know which one is best.

List item

If your data is flat, you could parse for keywords like 'Unit' or 'Task' to arrange your elements to the needed hierarchical order.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top