Completed
Push — master ( ae0103...9b98c5 )
by Joao
04:23 queued 56s
created

AnyDataset::appendRow()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4.0047

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 14
cts 15
cp 0.9333
rs 9.2
c 0
b 0
f 0
cc 4
eloc 14
nc 4
nop 1
crap 4.0047
1
<?php
2
3
namespace ByJG\AnyDataset\Dataset;
4
5
use ByJG\AnyDataset\Exception\DatabaseException;
6
use ByJG\Util\XmlUtil;
7
use ForceUTF8\Encoding;
8
use InvalidArgumentException;
9
10
/**
11
 * AnyDataset is a simple way to store data using only XML file.
12
 * Your structure is hierarquical and each "row" contains "fields" but these structure can vary for each row.
13
 * Anydataset files have extension ".anydata.xml" and have many classes to put and get data into anydataset xml file.
14
 * Anydataset class just read and write files. To search elements you need use AnyIterator
15
 * and IteratorFilter. Each row have a class Row.
16
17
 * XML Structure
18
 * <code>
19
 * <anydataset>
20
 *    <row>
21
 *        <field name="fieldname1">value of fieldname 1</field>
22
 *        <field name="fieldname2">value of fieldname 2</field>
23
 *        <field name="fieldname3">value of fieldname 3</field>
24
 *    </row>
25
 *    <row>
26
 *        <field name="fieldname1">value of fieldname 1</field>
27
 *        <field name="fieldname4">value of fieldname 4</field>
28
 *    </row>
29
 * </anydataset>
30
 * </code>
31
32
 * How to use:
33
 * <code>
34
 * $any = new AnyDataset();
35
 * </code>
36
37
 *
38
*@see Row
39
 * @see AnyIterator
40
 * @see IteratorFilter
41
42
 */
43
class AnyDataset
44
{
45
46
    /**
47
     * Internal structure represent the current Row
48
     *
49
*@var Row[]
50
     */
51
    private $collection;
52
53
    /**
54
     * Current node anydataset works
55
     * @var int
56
     */
57
    private $currentRow;
58
59
    /**
60
     * Path to anydataset file
61
     * @var string
62
     */
63
    private $path;
64
65
    /**
66
     *
67
     * @param string $file
68
     * @throws InvalidArgumentException
69
     */
70 19
    public function __construct($file = null)
71
    {
72 19
        $this->collection = array();
73 19
        $this->currentRow = -1;
74
75 19
        $this->path = null;
76 19
        if (!is_null($file)) {
77 7
            if (!is_string($file)) {
78
                throw new \InvalidArgumentException('I expected a string as a file name');
79
            }
80
81 7
            $ext = pathinfo($file, PATHINFO_EXTENSION);
82 7
            if (empty($ext)) {
83 7
                $file .= '.anydata.xml';
84 7
            }
85 7
            $this->path = $file;
86
87 7
            $this->createFrom($this->path);
88 7
        }
89 19
    }
90
91
    /**
92
     * Private method used to read and populate anydataset class from specified file
93
     * @param string $filepath Path and Filename to be read
94
     */
95 7
    private function createFrom($filepath)
96
    {
97 7
        if (file_exists($filepath)) {
98 7
            $anyDataSet = XmlUtil::createXmlDocumentFromFile($filepath);
99 7
            $this->collection = array();
100
101 7
            $rows = $anyDataSet->getElementsByTagName("row");
102 7
            foreach ($rows as $row) {
103 7
                $sr = new Row();
104 7
                $fields = $row->getElementsByTagName("field");
105 7
                foreach ($fields as $field) {
106 7
                    $attr = $field->attributes->getNamedItem("name");
107 7
                    if (is_null($attr)) {
108
                        throw new \InvalidArgumentException('Malformed anydataset file ' . basename($filepath));
109
                    }
110
111 7
                    $sr->addField($attr->nodeValue, $field->nodeValue);
112 7
                }
113 7
                $sr->acceptChanges();
114 7
                $this->collection[] = $sr;
115 7
            }
116 7
            $this->currentRow = count($this->collection) - 1;
117 7
        }
118 7
    }
119
120
    /**
121
     * Returns the AnyDataset XML representative structure.
122
     * @return string XML String
123
     */
124 1
    public function xml()
125
    {
126 1
        return $this->getAsDom()->saveXML();
127
    }
128
129
    /**
130
     * Returns the AnyDataset XmlDocument representive object
131
     * @return \DOMDocument XmlDocument object
132
     */
133 2
    public function getAsDom()
134
    {
135 2
        $anyDataSet = XmlUtil::createXmlDocumentFromStr("<anydataset></anydataset>");
136 2
        $nodeRoot = $anyDataSet->getElementsByTagName("anydataset")->item(0);
137 2
        foreach ($this->collection as $sr) {
138 2
            $row = $sr->getAsDom();
139 2
            $nodeRow = $row->getElementsByTagName("row")->item(0);
140 2
            $newRow = XmlUtil::createChild($nodeRoot, "row");
141 2
            XmlUtil::addNodeFromNode($newRow, $nodeRow);
142 2
        }
143
144 2
        return $anyDataSet;
145
    }
146
147
    /**
148
     *
149
     * @param string $file
150
     * @throws DatabaseException
151
     */
152 1
    public function save($file = null)
153
    {
154 1
        if (!is_null($file)) {
155 1
            if (!is_string($file)) {
156
                throw new InvalidArgumentException('Invalid file name');
157
            }
158
159 1
            $this->path = $file;
160 1
        }
161
162 1
        if (is_null($this->path)) {
163
            throw new DatabaseException("No such file path to save anydataset");
164
        }
165
166 1
        XmlUtil::saveXmlDocument($this->getAsDom(), $this->path);
167 1
    }
168
169
    /**
170
     * Append one row to AnyDataset.
171
     *
172
     * @param Row $singleRow
173
     * @return void
174
     */
175 14
    public function appendRow($singleRow = null)
176
    {
177 14
        if (!is_null($singleRow)) {
178 3
            if ($singleRow instanceof Row) {
179 2
                $this->collection[] = $singleRow;
180 2
                $singleRow->acceptChanges();
181 3
            } elseif (is_array($singleRow)) {
182 2
                $this->collection[] = new Row($singleRow);
183 2
            } else {
184
                throw new InvalidArgumentException("You must pass an array or a Row object");
185
            }
186 3
        } else {
187 11
            $singleRow = new Row();
188 11
            $this->collection[] = $singleRow;
189 11
            $singleRow->acceptChanges();
190
        }
191 14
        $this->currentRow = count($this->collection) - 1;
192 14
    }
193
194
    /**
195
     * Enter description here...
196
     *
197
     * @param GenericIterator $iterator
198
     */
199 1
    public function import($iterator)
200
    {
201 1
        foreach ($iterator as $singleRow) {
202 1
            $this->appendRow($singleRow);
203 1
        }
204 1
    }
205
206
    /**
207
     * Insert one row before specified position.
208
     * @param int $rowNumber
209
     * @param mixed $row
210
     */
211 1
    public function insertRowBefore($rowNumber, $row = null)
212
    {
213 1
        if ($rowNumber > count($this->collection)) {
214
            $this->appendRow($row);
215
        } else {
216 1
            $singleRow = $row;
217 1
            if (!($row instanceof Row)) {
218 1
                $singleRow = new Row($row);
219 1
            }
220 1
            array_splice($this->collection, $rowNumber, 0, '');
221 1
            $this->collection[$rowNumber] = $singleRow;
222
        }
223 1
    }
224
225
    /**
226
     *
227
     * @param mixed $row
228
     */
229 2
    public function removeRow($row = null)
230
    {
231 2
        if (is_null($row)) {
232
            $row = $this->currentRow;
233
        }
234 2
        if ($row instanceof Row) {
235
            $iPos = 0;
236
            foreach ($this->collection as $sr) {
237
                if ($sr->toArray() == $row->toArray()) {
238
                    $this->removeRow($iPos);
239
                    break;
240
                }
241
                $iPos++;
242
            }
243
            return;
244
        }
245
246 2
        if ($row == 0) {
247 1
            $this->collection = array_slice($this->collection, 1);
248 1
        } else {
249 1
            $this->collection = array_slice($this->collection, 0, $row) + array_slice($this->collection, $row);
250
        }
251 2
    }
252
253
    /**
254
     * Add a single string field to an existing row
255
     * @param string $name - Field name
256
     * @param string $value - Field value
257
     * @return void
258
     */
259 10
    public function addField($name, $value)
260
    {
261 10
        if ($this->currentRow < 0) {
262 2
            $this->appendRow();
263 2
        }
264 10
        $this->collection[$this->currentRow]->addField($name, $value);
265 10
    }
266
267
    /**
268
     * Get an Iterator filtered by an IteratorFilter
269
     * @param IteratorFilter $itf
270
     * @return GenericIterator
271
     */
272 18
    public function getIterator(IteratorFilter $itf = null)
273
    {
274 18
        if (is_null($itf)) {
275 18
            return new AnyIterator($this->collection);
276
        }
277
278
        return new AnyIterator($itf->match($this->collection));
279
    }
280
281
    /**
282
     * @desc
283
     * @param IteratorFilter $itf
284
     * @param string $fieldName
285
     * @return array
286
     */
287 1
    public function getArray($itf, $fieldName)
288
    {
289 1
        $iterator = $this->getIterator($itf);
290 1
        $result = array();
291 1
        while ($iterator->hasNext()) {
292 1
            $singleRow = $iterator->moveNext();
293 1
            $result [] = $singleRow->get($fieldName);
294 1
        }
295 1
        return $result;
296
    }
297
298
    /**
299
     *
300
     * @param string $field
301
     * @return void
302
     */
303 1
    public function sort($field)
304
    {
305 1
        if (count($this->collection) == 0) {
306
            return;
307
        }
308
309 1
        $this->collection = $this->quickSortExec($this->collection, $field);
310
311 1
        return;
312
    }
313
314
    /**
315
     * @param Row[] $seq
316
     * @param $field
317
     * @return array
318
     */
319 1
    protected function quickSortExec($seq, $field)
320
    {
321 1
        if (!count($seq)) {
322 1
            return $seq;
323
        }
324
325 1
        $key = $seq[0];
326 1
        $left = $right = array();
327
328 1
        $cntSeq = count($seq);
329 1
        for ($i = 1; $i < $cntSeq; $i ++) {
330 1
            if ($seq[$i]->get($field) <= $key->get($field)) {
331 1
                $left[] = $seq[$i];
332 1
            } else {
333 1
                $right[] = $seq[$i];
334
            }
335 1
        }
336
337 1
        return array_merge(
338 1
            $this->quickSortExec($left, $field),
339 1
            [ $key ],
340 1
            $this->quickSortExec($right, $field)
341 1
        );
342
    }
343
344
    /**
345
     * @param $document
346
     * @return array|string
347
     */
348
    public static function fixUTF8($document)
349
    {
350
        return Encoding::fixUTF8(Encoding::removeBOM($document), Encoding::ICONV_TRANSLIT);
351
    }
352
}
353