/** * The deserialize method is called during xml parsing. * * This method is called statictly, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are * free to return other data as well. * * You are responsible for advancing the reader to the next element. Not * doing anything will result in a never-ending loop. * * If you just want to skip parsing for this element altogether, you can * just call $reader->next(); * * $reader->parseInnerTree() will parse the entire sub-tree, and advance to * the next element. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $result = ['name' => null, 'is-not-defined' => false, 'param-filters' => [], 'text-match' => null, 'time-range' => false]; $att = $reader->parseAttributes(); $result['name'] = $att['name']; $elems = $reader->parseInnerTree(); if (is_array($elems)) { foreach ($elems as $elem) { switch ($elem['name']) { case '{' . Plugin::NS_CALDAV . '}param-filter': $result['param-filters'][] = $elem['value']; break; case '{' . Plugin::NS_CALDAV . '}is-not-defined': $result['is-not-defined'] = true; break; case '{' . Plugin::NS_CALDAV . '}time-range': $result['time-range'] = ['start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null, 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null]; if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) { throw new BadRequest('The end-date must be larger than the start-date'); } break; case '{' . Plugin::NS_CALDAV . '}text-match': $result['text-match'] = ['negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes', 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap', 'value' => $elem['value']]; break; } } } return $result; }
/** * The deserialize method is called during xml parsing. * * This method is called statictly, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are * free to return other data as well. * * You are responsible for advancing the reader to the next element. Not * doing anything will result in a never-ending loop. * * If you just want to skip parsing for this element altogether, you can * just call $reader->next(); * * $reader->parseInnerTree() will parse the entire sub-tree, and advance to * the next element. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $result = ['name' => null, 'test' => 'anyof', 'is-not-defined' => false, 'param-filters' => [], 'text-matches' => []]; $att = $reader->parseAttributes(); $result['name'] = $att['name']; if (isset($att['test']) && $att['test'] === 'allof') { $result['test'] = 'allof'; } $elems = $reader->parseInnerTree(); if (is_array($elems)) { foreach ($elems as $elem) { switch ($elem['name']) { case '{' . Plugin::NS_CARDDAV . '}param-filter': $result['param-filters'][] = $elem['value']; break; case '{' . Plugin::NS_CARDDAV . '}is-not-defined': $result['is-not-defined'] = true; break; case '{' . Plugin::NS_CARDDAV . '}text-match': $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains'; if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) { throw new BadRequest('Unknown match-type: ' . $matchType); } $result['text-matches'][] = ['negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes', 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap', 'value' => $elem['value'], 'match-type' => $matchType]; break; } } } return $result; }
/** * The deserialize method is called during xml parsing. * * This method is called statictly, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are * free to return other data as well. * * You are responsible for advancing the reader to the next element. Not * doing anything will result in a never-ending loop. * * If you just want to skip parsing for this element altogether, you can * just call $reader->next(); * * $reader->parseInnerTree() will parse the entire sub-tree, and advance to * the next element. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $result = ['name' => null, 'is-not-defined' => false, 'comp-filters' => [], 'prop-filters' => [], 'time-range' => false]; $att = $reader->parseAttributes(); $result['name'] = $att['name']; $elems = $reader->parseInnerTree(); if (is_array($elems)) { foreach ($elems as $elem) { switch ($elem['name']) { case '{' . Plugin::NS_CALDAV . '}comp-filter': $result['comp-filters'][] = $elem['value']; break; case '{' . Plugin::NS_CALDAV . '}prop-filter': $result['prop-filters'][] = $elem['value']; break; case '{' . Plugin::NS_CALDAV . '}is-not-defined': $result['is-not-defined'] = true; break; case '{' . Plugin::NS_CALDAV . '}time-range': if ($result['name'] === 'VCALENDAR') { throw new BadRequest('You cannot add time-range filters on the VCALENDAR component'); } $result['time-range'] = ['start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null, 'end' => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null]; if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) { throw new BadRequest('The end-date must be larger than the start-date'); } break; } } } return $result; }
/** * The deserialize method is called during xml parsing. * * This method is called statictly, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are * free to return other data as well. * * Important note 2: You are responsible for advancing the reader to the * next element. Not doing anything will result in a never-ending loop. * * If you just want to skip parsing for this element altogether, you can * just call $reader->next(); * * $reader->parseInnerTree() will parse the entire sub-tree, and advance to * the next element. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $result = ['name' => null, 'is-not-defined' => false, 'text-match' => null]; $att = $reader->parseAttributes(); $result['name'] = $att['name']; $elems = $reader->parseInnerTree(); if (is_array($elems)) { foreach ($elems as $elem) { switch ($elem['name']) { case '{' . Plugin::NS_CALDAV . '}is-not-defined': $result['is-not-defined'] = true; break; case '{' . Plugin::NS_CALDAV . '}text-match': $result['text-match'] = ['negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes', 'collation' => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap', 'value' => $elem['value']]; break; } } } return $result; }
/** * Convert XML Elements to IodefElement Objects * @param SabreReader $reader [description] * @return IodefElement */ public static function xmlDeserialize(SabreReader $reader) { $IodefElement = new static(); $IodefElement->setAttributes($reader->parseAttributes()); $innerData = $reader->parseInnerTree(); // parseInnerTree will return an array or string. if (is_array($innerData)) { foreach ($innerData as $child) { // This should always and only by of type 'object'. if (gettype($child['value']) == 'object') { $className = $child['value']->getShortName(); if (substr($IodefElement->elements[$className], -6) == '_MULTI') { $IodefElement->{$className}[] = $child['value']; } else { $IodefElement->{$className} = $child['value']; } } } } else { if (!empty($innerData)) { $IodefElement->value = $innerData; } } return $IodefElement; }
/** * Parse the curriculum definition file. * * By passing the official curriculum definition file (XML), this method * will parse it and return a curriculum definition it can understand and * treat. It mainly needs a "dictionary" of term types. * * @param string $curriculumXml * The curriculum definition file, in XML. * @param string $variant * (optional) The variant of the curriculum to parse. Defaults to 'V_EF'. * * @return array * An object with 2 properties: * - curriculum: A parsed and prepared curriculum tree. It uses * Educa\DSB\Client\Curriculum\Term\LP21Term elements to define * the curriculum tree. * - dictionary: A dictionary of term identifiers, with name and type * information for each one of them. * * @see \Educa\DSB\Client\Curriculum\LP21Curriculum::setCurriculumDictionary() */ public static function parseCurriculumXml($curriculumXml, $variant = 'V_EF') { $reader = new Reader(); // Prepare custom handlers for reading an XML node. See the Sabre\Xml // documentation for more information. $baseHandler = function ($reader) use($variant) { $node = new \stdClass(); // Fetch the attributes. We want the UUID attribute. $attributes = $reader->parseAttributes(); $node->uuid = trim($attributes['uuid']); // We derive the type from the node name. $node->type = strtolower(str_replace('{}', '', trim($reader->getClark()))); // Give a default description. $node->description = (object) array('de' => ''); // Fetch the descendants. $children = $reader->parseInnerTree(); if (!empty($children)) { $node->children = array(); foreach ($children as $child) { // Look for child types that are relevant for us. Some must // be parsed as child types of their own, others should be // treated as being part of the current node. if (in_array($child['name'], array('{}fach', '{}kompetenzbereich', '{}handlungs-themenaspekt', '{}kompetenz', '{}kompetenzstufe'))) { $node->children[] = $child; } elseif ($child['name'] == '{}bezeichnung') { $node->description = (object) array_reduce($child['value'], function ($carry, $item) { $langcode = strtolower(str_replace('{}', '', $item['name'])); $carry[$langcode] = $item['value']; return $carry; }, array()); } elseif ($child['name'] == '{}kantone') { $node->cantons = array_map('trim', explode(',', $child['value'])); } } } if (!empty($node->cantons) && !in_array($variant, $node->cantons)) { return null; } return $node; }; $kompetenzstufeHandler = function ($reader) use($variant) { $nodes = array(); $cycle = $url = $version = $code = null; // Fetch the descendants. $children = $reader->parseInnerTree(); if (!empty($children)) { foreach ($children as $child) { if ($child['name'] == '{}absaetze') { $nodes = $child['value']; } elseif ($child['name'] == '{}zyklus') { $cycle = trim($child['value']); } elseif ($child['name'] == '{}lehrplanversion') { $version = trim($child['value']); } elseif ($child['name'] == '{}kanton' && $child['attributes']['id'] == $variant) { foreach ($child['value'] as $grandChild) { if ($grandChild['name'] == '{}code') { $code = trim($grandChild['value']); } elseif ($grandChild['name'] == '{}url') { $url = trim($grandChild['value']); } } } } } // Map all the Kompetenzstufe properties to the child Absaetzen. return array_map(function ($node) use($cycle, $url, $version, $code) { if (isset($cycle)) { $node->cycle = $cycle; } if (isset($url)) { $node->url = $url; } if (isset($version)) { $node->version = $version; } if (isset($code)) { $node->code = $code; } return $node; }, $nodes); }; $absaetzeHandler = function ($reader) { $nodes = array(); // Fetch the descendants. $children = $reader->parseInnerTree(); if (!empty($children)) { foreach ($children as $child) { if ($child['name'] == '{}bezeichnung') { $node = new \stdClass(); // We treat it as a "Kompetenzstufe". $node->type = 'kompetenzstufe'; $node->description = (object) array_reduce($child['value'], function ($carry, $item) { $langcode = strtolower(str_replace('{}', '', $item['name'])); $carry[$langcode] = $item['value']; return $carry; }, array()); // The UUID is on the child Bezeichnung element, not our // own node. $node->uuid = $child['attributes']['uuid']; $nodes[] = $node; } } } return $nodes; }; // Register our handler for the following node types. All others will be // treated with the default one provided by Sabre\Xml, but we don't // really care. $reader->elementMap = ['{}fachbereich' => $baseHandler, '{}fach' => $baseHandler, '{}kompetenzbereich' => $baseHandler, '{}handlungs-themenaspekt' => $baseHandler, '{}kompetenz' => $baseHandler, '{}kompetenzstufe' => $kompetenzstufeHandler, '{}absaetze' => $absaetzeHandler]; // Parse the data. $reader->xml($curriculumXml); $data = $reader->parse(); // Prepare the dictionary. $dictionary = array(); // Prepare our root element. $root = new LP21Term('root', 'root'); // Now, recursively parse the tree, transforming it into a tree of // LP21Term instances. $recurse = function ($tree, $parent) use(&$recurse, &$dictionary) { foreach ($tree as $item) { // Fetch our nodes. $nodes = $item['value']; if (!is_array($nodes)) { $nodes = [$nodes]; } // Double check the format. Is this one of our nodes? foreach ($nodes as $node) { if (isset($node->uuid) && isset($node->type) && isset($node->description)) { $term = new LP21Term($node->type, $node->uuid, $node->description); $parent->addChild($term); // Add it to our dictionary. $dictionary[$node->uuid] = (object) array('name' => $node->description, 'type' => $node->type); // Do we have an objective code? if (!empty($node->code)) { $term->setCode($node->code); $dictionary[$node->uuid]->code = $node->code; } // Do we have any cantons information? if (!empty($node->cantons)) { $term->setCantons($node->cantons); $dictionary[$node->uuid]->cantons = $node->cantons; } // Do we have curriculum version information? if (!empty($node->version)) { $term->setVersion($node->version); $dictionary[$node->uuid]->version = $node->version; } // Do we have URL information? if (!empty($node->url)) { $term->setUrl($node->url); $dictionary[$node->uuid]->url = $node->url; } // Do we have cycle information? if (!empty($node->cycle)) { $cycles = str_split($node->cycle); $term->setCycles($cycles); $dictionary[$node->uuid]->cycles = $cycles; } if (!empty($node->children)) { $recurse($node->children, $term); } } } } }; $recurse($data['value'], $root); // Return the parsed data. return (object) array('curriculum' => $root, 'dictionary' => $dictionary); }