13namespace Depage\XmlDb\Helpers;
15use Depage\XmlDb\Interfaces\DocumentWriterHost;
16use Depage\XmlDb\XmlNs;
38 private $insertNodeQuery =
null;
53 protected string $table,
55 protected XmlNs $dbNs,
56 protected string $idAttribute,
73 if (!empty($preference)) {
74 $ids = str_repeat(
'?,', count($preference) - 1) .
'?';
75 $query = $this->pdo->prepare(
"SELECT id FROM {$this->table} WHERE id IN ($ids)");
76 $query->execute($preference);
77 $results = $query->fetchAll(\PDO::FETCH_COLUMN);
79 $free = array_keys(array_flip(array_diff($preference, $results)));
97 $targetId = $this->host->getParentIdById($nodeId);
99 if ($this->host->getDoctypeHandler()->onDeleteNode($nodeId, $targetId)) {
101 $query = $this->pdo->prepare(
102 "DELETE FROM {$this->table}
103 WHERE id_doc = :doc_id AND id = :node_id",
107 'doc_id' => $this->docId,
108 'node_id' => $nodeId,
110 }
catch (\PDOException $e) {
111 $childquery = $this->pdo->prepare(
112 TreeSql::descendants($this->table,
"id = :id AND id_doc = :doc_id", $this->host::SORTKEY_PAD) .
"
113 SELECT id FROM tree ORDER BY sortkey DESC;",
115 $childquery->execute([
116 'doc_id' => $this->docId,
123 $childIds = $childquery->fetchAll(\PDO::FETCH_COLUMN);
124 $childquery->closeCursor();
126 foreach ($childIds as $childId) {
128 'doc_id' => $this->docId,
129 'node_id' => $childId,
148 public function moveNode($nodeId, $targetId, $targetIndex)
151 $nodeParentId = $this->host->getParentIdById($nodeId);
152 $nodeIndex = $this->positions->indexById($nodeId);
158 $targetIndex = $this->positions->normalizeIndex(
161 ($targetId == $nodeParentId) ? $nodeId :
null
164 if ($targetId != $nodeParentId || $targetIndex != $nodeIndex) {
168 $pos = $this->positions->positionForIndex($targetId, $targetIndex, $nodeId);
170 $query = $this->pdo->prepare(
171 "UPDATE {$this->table}
172 SET id_doc = :doc_id, id_parent = :target_id, pos = :pos
173 WHERE id = :node_id AND id_doc = :node_doc_id",
176 'target_id' => $targetId,
178 'node_id' => $nodeId,
179 'doc_id' => $this->docId,
180 'node_doc_id' => $this->docId,
183 $this->host->updateLastChange();
184 $result = $nodeParentId;
199 public function copyNode($nodeId, $targetId, $targetIndex)
201 $xmlDoc = $this->host->getSubdocByNodeId($nodeId,
false);
203 $saveId = $this->
saveNodeIn($rootNode, $targetId, $targetIndex,
true);
215 $query = $this->pdo->prepare(
216 "UPDATE {$this->table} AS xml
217 SET xml.value = :attr_str
218 WHERE xml.id = :node_id AND xml.id_doc = :doc_id",
220 $success = $query->execute([
221 'node_id' => $nodeId,
223 'doc_id' => $this->docId,
226 $this->host->updateLastChange();
240 $rootId = $nodeArray[0][
'id'];
242 if (is_null($rootId)) {
247 $targetId = $this->host->getParentIdById($rootId);
248 $targetIndex = $this->positions->indexById($rootId);
250 if ($targetId ===
false) {
253 if ($targetIndex ===
false) {
261 return $this->
saveNodeArray($nodeArray, $targetId, $targetIndex,
true);
274 public function saveNodeIn($node, $targetId, $targetIndex, $incChildren)
276 $parentId = $this->host->getParentIdById($targetId);
279 if ($parentId ===
false) {
280 $this->pdo->exec(
'SET foreign_key_checks = 0;');
283 $query = $this->pdo->prepare(
286 WHERE id_doc = :doc_id",
289 'doc_id' => $this->docId,
294 $this->pdo->exec(
'SET foreign_key_checks = 1;');
300 $targetIndex = $this->positions->normalizeIndex($targetId, $targetIndex);
306 return $this->
saveNodeArray($nodeArray, $targetId, $targetIndex, $incChildren);
313 public function saveNodeArray($nodeArray, $targetId, $targetIndex, $incChildren)
317 foreach ($nodeArray as $node) {
319 $ids[] = $node[
'id'];
325 foreach ($nodeArray as $i => $node) {
326 if (!is_null($node[
'id'])) {
327 $index = array_search($node[
'id'], $free);
329 if ($index ===
false) {
330 $nodeArray[$i][
'id'] =
null;
332 array_splice($free, $index, 1);
337 foreach ($nodeArray as $i => $node) {
338 if (is_null($node[
'id'])) {
339 $nodeArray[$i][
'id'] = array_shift($free);
344 $nodeArray[0][
'id'] = $this->
saveNodeToDb($nodeArray[0][
'node'], $nodeArray[0][
'id'], $targetId, $targetIndex,
true);
345 $this->insertNodeQuery =
null;
354 $this->host->updateLastChange();
356 return $nodeArray[0][
'id'];
364 $batch =
new NodeBatch($this->pdo, $this->table, (
int) $this->docId, $this->host::SAVE_BATCH_SIZE);
373 foreach ($nodeArray as $i => $node) {
377 $depths[$i] = $depths[$node[
'parent_index']] + 1;
378 $maxDepth = max($maxDepth, $depths[$i]);
381 for ($level = 1; $level <= $maxDepth; $level++) {
382 foreach ($nodeArray as $i => $node) {
384 $depths[$i] != $level
385 || (($node[
'node']->nodeType == XML_ELEMENT_NODE) != $xmlElementNode)
390 $parentId = $nodeArray[$node[
'parent_index']][
'id'];
393 $value = \Normalizer::normalize($value);
395 $batch->add($i, $node[
'id'], $parentId, ($node[
'pos'] + 1) *
Positions::POS_GAP, $name, $value, $type);
397 if ($batch->isFull()) {
409 $this->insertNodeQuery =
null;
424 foreach ($resolvedIds as $index => $id) {
425 $nodeArray[$index][
'id'] = $id;
427 if ($nodeArray[$index][
'node']->nodeType == XML_ELEMENT_NODE) {
428 $nodeArray[$index][
'node']->setAttributeNS(
430 $this->dbNs->ns .
':' . $this->idAttribute,
451 $type = $node->nodeType;
453 if ($type === XML_DOCUMENT_NODE) {
454 $rootNode = $node->documentElement;
456 } elseif ($type === XML_ELEMENT_NODE) {
461 'parent_index' => $parentIndex,
466 $parentIndex = count($nodeArray) - 1;
467 $nodeName = (($node->prefix !=
'') ? $node->prefix .
':' :
'') . $node->localName;
469 $preserveWhitespace = $this->host->getDoctypeHandler()->getPreserveWhitespace();
473 || in_array($nodeName, $preserveWhitespace)
475 $stripwhitespace =
false;
478 $tmpNode = $node->firstChild;
480 while ($tmpNode !=
null) {
482 $tmpNode->nodeType != XML_TEXT_NODE
485 || trim($tmpNode->textContent) !=
''
491 $tmpNode = $tmpNode->nextSibling;
498 'parent_index' => $parentIndex,
518 public function saveNodeToDb($node, $id, $targetId, $targetIndex, $setPosition =
false)
520 if (is_null($this->insertNodeQuery)) {
521 $this->insertNodeQuery = $this->pdo->prepare(
522 "INSERT {$this->table}
525 id_parent = :target_id,
531 ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id)",
535 if (is_null($id) || !is_numeric($id)) {
538 $idQuery = (int) $id;
541 if ($targetId !==
null && $setPosition) {
546 $targetIndex = $this->positions->positionForIndex($targetId, $targetIndex);
551 $nodeData = \Normalizer::normalize($nodeData);
553 $this->insertNodeQuery->execute([
554 'id_query' => $idQuery,
555 'target_id' => $targetId,
556 'pos' => $targetIndex,
558 'value' => $nodeData,
560 'doc_id' => $this->docId,
564 $id = $this->pdo->lastInsertId();
567 if ($node->nodeType == XML_ELEMENT_NODE) {
568 $node->setAttributeNS($this->dbNs->uri, $this->dbNs->ns .
':' . $this->idAttribute, $id);
getFreeNodeIds($preference=[])
gets unused db-node-ids for saving nodes
saveNodesByType(&$nodeArray, $xmlElementNode)
writes all nodes of one type (element nodes first, the other node types after them) level by level
__construct( $pdo, protected string $table, protected $docId, protected XmlNs $dbNs, protected string $idAttribute, protected Positions $positions, protected DocumentWriterHost $host)
saveNodeToDb($node, $id, $targetId, $targetIndex, $setPosition=false)
saves a node to database
saveNodeIn($node, $targetId, $targetIndex, $incChildren)
saves a node into the child list of a target node
saveNodePrivate($node)
re-saves a node in place: the subtree replaces the stored nodes under the db:id its root carries
deleteNode($nodeId)
deletes a node in the database
saveNodeArray($nodeArray, $targetId, $targetIndex, $incChildren)
writes a flattened node tree; the ids the nodes carry are reused when they are free,...
saveAttributes($nodeId, $attributes)
writes the attribute string of a node
setResolvedNodeIds($resolvedIds, &$nodeArray)
stores the database ids a batch insert handed out for its nodes in the list of nodes to save (the par...
copyNode($nodeId, $targetId, $targetIndex)
copy node in database
moveNode($nodeId, $targetId, $targetIndex)
moves node in database
getNodeArrayForSaving(&$nodeArray, $node, $parentIndex=null, $pos=0, $stripwhitespace=true)
gets all nodes of a document in one array
static attributeString(array $attributes, XmlNs $dbNs, string $idAttribute)
builds the attribute string a node's value column holds from the attributes of a node,...
static dbIdOf($node, XmlNs $dbNs, string $idAttribute)
reads the database id out of the db:id attribute of a node ('' if the node does not carry one)
static fromDomNode($node, XmlNs $dbNs, string $idAttribute)
returns the (type, name, value) tupel a DOM node is saved as, in exactly the same way saveNodeToDb st...
const POS_GAP
distance between the sparse positions of sibling nodes
static descendants(string $table, string $anchorCondition, int $pad)
builds the recursive common table expression selecting a node matched by $anchor and all of its desce...
A namespace prefix and the URI it is bound to.
what the write pipeline (Helpers\DocumentWriter) needs from the document it writes: the document meta...