├── .gitignore ├── src ├── Offer │ ├── BookOffer.php │ ├── VendorModelOffer.php │ └── Offer.php ├── Exception │ └── YMLException.php ├── Param.php ├── Storage.php ├── Factory │ └── Factory.php ├── Category.php ├── Currency.php ├── Parser.php ├── Shop.php └── Builder.php ├── composer.json ├── README.md ├── phpunit.xml.dist └── Tests ├── ParserTest.php └── Fixtures ├── shops.dtd └── yml.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | composer.lock 4 | phpunit.xml 5 | -------------------------------------------------------------------------------- /src/Offer/BookOffer.php: -------------------------------------------------------------------------------- 1 | =4.0" 13 | }, 14 | "autoload": { 15 | "psr-4": { "Sirian\\YMLParser\\": "src/" } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # YandexMarketLanguage Parser # 3 | 4 | ## About ## 5 | 6 | [Yandex Market Language](https://yandex.ru/support/partnermarket/yml/about-yml.xml) parser for PHP. 7 | 8 | 9 | ## 1. Installation ## 10 | 11 | Add the `sirian/yandex-market-language-parser` package to your `require` section in the `composer.json` file. 12 | 13 | ``` bash 14 | $ composer require sirian/yandex-market-language-parser 15 | ``` 16 | 17 | ## 2. Usage ## 18 | 19 | ```php 20 | 21 | use Sirian\YMLParser\Parser; 22 | 23 | $parser = new Parser(); 24 | $result = $parser->parse($file); 25 | 26 | $shop = $result->getShop(); 27 | 28 | foreach ($result->getOffers() as $offer) { 29 | // some logic 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./Tests/ 17 | 18 | 19 | 20 | 21 | 22 | ./src 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Param.php: -------------------------------------------------------------------------------- 1 | name; 14 | } 15 | 16 | public function setName($name) 17 | { 18 | $this->name = $name; 19 | return $this; 20 | } 21 | 22 | public function getUnit() 23 | { 24 | return $this->unit; 25 | } 26 | 27 | public function setUnit($unit) 28 | { 29 | $this->unit = $unit; 30 | return $this; 31 | } 32 | 33 | public function getValue() 34 | { 35 | return $this->value; 36 | } 37 | 38 | public function setValue($value) 39 | { 40 | $this->value = $value; 41 | return $this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Offer/VendorModelOffer.php: -------------------------------------------------------------------------------- 1 | vendor; 14 | } 15 | 16 | public function setVendor($vendor) 17 | { 18 | $this->vendor = $vendor; 19 | return $this; 20 | } 21 | 22 | public function getVendorCode() 23 | { 24 | return $this->vendorCode; 25 | } 26 | 27 | public function setVendorCode($vendorCode) 28 | { 29 | $this->vendorCode = $vendorCode; 30 | return $this; 31 | } 32 | 33 | public function getModel() 34 | { 35 | return $this->model; 36 | } 37 | 38 | public function setModel($model) 39 | { 40 | $this->model = $model; 41 | return $this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Storage.php: -------------------------------------------------------------------------------- 1 | file = tmpfile(); 16 | } 17 | 18 | public function addOfferXML($xml) 19 | { 20 | $this->offersCount++; 21 | fputs($this->file, json_encode($xml, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) . "\n"); 22 | } 23 | 24 | public function getShopXML() 25 | { 26 | return $this->shopXML; 27 | } 28 | 29 | public function setShopXML($shopXML) 30 | { 31 | $this->shopXML = $shopXML; 32 | 33 | return $this; 34 | } 35 | 36 | public function getNextOfferXML() 37 | { 38 | fseek($this->file, 0); 39 | while ($line = fgets($this->file)) { 40 | yield $line; 41 | } 42 | } 43 | 44 | public function getOffersCount() 45 | { 46 | return $this->offersCount; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Factory/Factory.php: -------------------------------------------------------------------------------- 1 | id; 29 | } 30 | 31 | public function setId($id) 32 | { 33 | $this->id = $id; 34 | return $this; 35 | } 36 | 37 | public function getParent() 38 | { 39 | return $this->parent; 40 | } 41 | 42 | public function setParent(Category $parent = null) 43 | { 44 | $this->parent = $parent; 45 | return $this; 46 | } 47 | 48 | public function getName() 49 | { 50 | return $this->name; 51 | } 52 | 53 | public function setName($name) 54 | { 55 | $this->name = $name; 56 | return $this; 57 | } 58 | 59 | public function hasParent() 60 | { 61 | return null !== $this->parent; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Tests/ParserTest.php: -------------------------------------------------------------------------------- 1 | parse($file); 22 | 23 | $shop = $result->getShop(); 24 | 25 | $xml = simplexml_load_file($file); 26 | $xml = $xml->shop; 27 | 28 | $this->assertCount(3, $shop->getCurrencies()); 29 | $this->assertCount(19, $shop->getCategories()); 30 | $this->assertEquals((string)$xml->name, $shop->getName()); 31 | $this->assertEquals((string)$xml->url, $shop->getUrl()); 32 | $this->assertEquals((string)$xml->company, $shop->getCompany()); 33 | $this->assertEquals(7, $shop->getOffersCount()); 34 | 35 | $offers = iterator_to_array($result->getOffers()); 36 | 37 | $this->assertCount(7, $offers); 38 | 39 | $offer = $offers[0]; 40 | 41 | $this->assertEquals(3, $offer->getParam('скорость')->getValue()); 42 | $this->assertEquals('true', $offer->getAttribute('available')); 43 | $this->assertEquals(25000, $offer->getOldPrice()); 44 | $this->assertEquals(15000, $offer->getPrice()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Currency.php: -------------------------------------------------------------------------------- 1 | id; 58 | } 59 | 60 | public function setId($id) 61 | { 62 | $this->id = $id; 63 | return $this; 64 | } 65 | 66 | public function getRate() 67 | { 68 | return $this->rate; 69 | } 70 | 71 | public function setRate($rate) 72 | { 73 | $this->rate = $rate; 74 | return $this; 75 | } 76 | 77 | public function getPlus() 78 | { 79 | return $this->plus; 80 | } 81 | 82 | public function setPlus($plus) 83 | { 84 | $this->plus = $plus; 85 | return $this; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Parser.php: -------------------------------------------------------------------------------- 1 | xmlReader = new \XMLReader(); 22 | $this->factory = $factory; 23 | } 24 | 25 | public function parse($file) 26 | { 27 | $this->path = []; 28 | 29 | $this->xmlReader->open($file); 30 | $result = $this->read(); 31 | $this->xmlReader->close(); 32 | 33 | return $result; 34 | } 35 | 36 | protected function read() 37 | { 38 | $xml = $this->xmlReader; 39 | $shopXML = ''; 40 | if (!$this->moveToShop()) { 41 | throw new YMLException('Invalid YML file'); 42 | } 43 | 44 | if (!$this->stepIn()) { 45 | throw new YMLException('Invalid YML file'); 46 | } 47 | 48 | $storage = new Storage(); 49 | 50 | do { 51 | if ('yml_catalog/shop/offers' == $this->getPath()) { 52 | if ($this->stepIn()) { 53 | do { 54 | $storage->addOfferXML($xml->readOuterXml()); 55 | } while ($this->moveToNextSibling()); 56 | } 57 | } else { 58 | $shopXML .= $xml->readOuterXml(); 59 | } 60 | } while ($this->moveToNextSibling()); 61 | 62 | $shopXML = '' . $shopXML . ''; 63 | 64 | $storage->setShopXML($shopXML); 65 | 66 | return new Builder($this->factory, $storage); 67 | } 68 | 69 | 70 | 71 | private function moveToShop() 72 | { 73 | $xml = $this->xmlReader; 74 | 75 | while ($xml->read()) { 76 | if ($xml->nodeType == \XMLReader::END_ELEMENT) { 77 | array_pop($this->path); 78 | continue; 79 | } 80 | 81 | 82 | if ($xml->nodeType !== \XMLReader::ELEMENT || $xml->isEmptyElement) { 83 | continue; 84 | } 85 | 86 | array_push($this->path, $xml->name); 87 | 88 | if ('yml_catalog/shop' === $this->getPath()) { 89 | return true; 90 | } 91 | } 92 | 93 | return false; 94 | } 95 | 96 | private function getPath() 97 | { 98 | return implode('/', $this->path); 99 | } 100 | 101 | private function stepIn() 102 | { 103 | $xml = $this->xmlReader; 104 | if ($xml->isEmptyElement) { 105 | return false; 106 | } 107 | while ($xml->read()) { 108 | if (\XMLReader::ELEMENT == $xml->nodeType) { 109 | array_push($this->path, $xml->name); 110 | return true; 111 | } 112 | if (\XMLReader::END_ELEMENT == $xml->nodeType) { 113 | array_pop($this->path); 114 | return false; 115 | } 116 | } 117 | return false; 118 | } 119 | 120 | private function moveToNextSibling() 121 | { 122 | $xml = $this->xmlReader; 123 | array_pop($this->path); 124 | while ($xml->next()) { 125 | if (\XMLReader::ELEMENT == $xml->nodeType) { 126 | array_push($this->path, $xml->name); 127 | return true; 128 | } 129 | 130 | if (\XMLReader::END_ELEMENT == $xml->nodeType) { 131 | return false; 132 | } 133 | } 134 | return false; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/Shop.php: -------------------------------------------------------------------------------- 1 | name; 84 | } 85 | 86 | public function setName($name) 87 | { 88 | $this->name = $name; 89 | return $this; 90 | } 91 | 92 | public function getCompany() 93 | { 94 | return $this->company; 95 | } 96 | 97 | public function setCompany($company) 98 | { 99 | $this->company = $company; 100 | return $this; 101 | } 102 | 103 | public function getUrl() 104 | { 105 | return $this->url; 106 | } 107 | 108 | public function setUrl($url) 109 | { 110 | $this->url = $url; 111 | return $this; 112 | } 113 | 114 | public function getPlatform() 115 | { 116 | return $this->platform; 117 | } 118 | 119 | public function setPlatform($platform) 120 | { 121 | $this->platform = $platform; 122 | return $this; 123 | } 124 | 125 | public function getVersion() 126 | { 127 | return $this->version; 128 | } 129 | 130 | public function setVersion($version) 131 | { 132 | $this->version = $version; 133 | return $this; 134 | } 135 | 136 | public function getAgency() 137 | { 138 | return $this->agency; 139 | } 140 | 141 | public function setAgency($agency) 142 | { 143 | $this->agency = $agency; 144 | return $this; 145 | } 146 | 147 | public function getEmail() 148 | { 149 | return $this->email; 150 | } 151 | 152 | public function setEmail($email) 153 | { 154 | $this->email = $email; 155 | return $this; 156 | } 157 | 158 | public function addCurrency(Currency $currency) 159 | { 160 | $this->currencies[$currency->getId()] = $currency; 161 | return $this; 162 | } 163 | 164 | public function addCategory(Category $category) 165 | { 166 | $this->categories[$category->getId()] = $category; 167 | return $this; 168 | } 169 | 170 | public function getCategory($id) 171 | { 172 | return isset($this->categories[$id]) ? $this->categories[$id] : null; 173 | } 174 | 175 | public function getCategories() 176 | { 177 | return $this->categories; 178 | } 179 | 180 | public function getCurrency($id) 181 | { 182 | return isset($this->currencies[$id]) ? $this->currencies[$id] : null; 183 | } 184 | 185 | public function getCurrencies() 186 | { 187 | return $this->currencies; 188 | } 189 | 190 | public function getOffersCount() 191 | { 192 | return $this->offersCount; 193 | } 194 | 195 | public function setOffersCount($offersCount) 196 | { 197 | $this->offersCount = $offersCount; 198 | 199 | return $this; 200 | } 201 | 202 | public function getXml() 203 | { 204 | return $this->xml; 205 | } 206 | 207 | public function setXml($xml) 208 | { 209 | $this->xml = $xml; 210 | 211 | return $this; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Tests/Fixtures/shops.dtd: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 43 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 136 | 137 | 138 | 139 | 140 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/Builder.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 28 | $this->storage = $storage; 29 | } 30 | 31 | public function getShop() 32 | { 33 | if (null == $this->shop) { 34 | $this->buildShop(); 35 | } 36 | return $this->shop; 37 | } 38 | 39 | /** 40 | * @return Offer[] 41 | */ 42 | public function getOffers() 43 | { 44 | $count = 0; 45 | foreach ($this->storage->getNextOfferXML() as $xml) { 46 | $xml = simplexml_load_string(json_decode($xml)); 47 | $offer = $this->buildOffer($xml); 48 | yield $count => $offer; 49 | $count++; 50 | } 51 | } 52 | 53 | protected function buildShop() 54 | { 55 | $this->shop = $this->factory->createShop(); 56 | $xml = simplexml_load_string($this->storage->getShopXML()); 57 | 58 | foreach ($xml->xpath('//currencies/currency') as $elem) { 59 | $this->shop->addCurrency($this->buildCurrency($elem)); 60 | } 61 | 62 | $this->buildCategories($xml); 63 | 64 | $this 65 | ->shop 66 | ->setName((string)$xml->name) 67 | ->setAgency((string)$xml->agency) 68 | ->setEmail((string)$xml->email) 69 | ->setCompany((string)$xml->company) 70 | ->setPlatform((string)$xml->platform) 71 | ->setUrl((string)$xml->url) 72 | ->setVersion((string)$xml->version) 73 | ->setOffersCount($this->storage->getOffersCount()) 74 | ->setXml($xml) 75 | ; 76 | } 77 | 78 | protected function createParam(\SimpleXMLElement $xml) 79 | { 80 | $name = (string)$xml['name']; 81 | $unit = (string)$xml['unit']; 82 | 83 | $param = $this->factory->createParam(); 84 | 85 | $param 86 | ->setName($name) 87 | ->setUnit($unit) 88 | ->setValue((string)$xml) 89 | ; 90 | 91 | return $param; 92 | } 93 | 94 | protected function buildCategories(\SimpleXMLElement $xml) 95 | { 96 | $parents = []; 97 | foreach ($xml->xpath('//categories/category') as $xml) { 98 | $this->getShop()->addCategory($this->buildCategory($xml)); 99 | 100 | foreach (['parentId', 'parent_id'] as $field) { 101 | if (isset($xml[$field])) { 102 | $parents[(string)$xml['id']] = (string)$xml[$field]; 103 | break; 104 | } 105 | } 106 | } 107 | 108 | foreach ($parents as $id => $parentId) { 109 | if ($id != $parentId) { 110 | $parent = $this->getShop()->getCategory($parentId); 111 | } else { 112 | $parent = null; 113 | } 114 | $this 115 | ->getShop() 116 | ->getCategory($id) 117 | ->setParent($parent) 118 | ; 119 | } 120 | } 121 | 122 | protected function buildCurrency(\SimpleXMLElement $xml) 123 | { 124 | $id = Currency::normalize((string)$xml['id']); 125 | 126 | $currency = $this->factory->createCurrency(); 127 | $currency 128 | ->setId($id) 129 | ->setRate((string)$xml['rate']) 130 | ->setPlus((int)$xml['plus']) 131 | ; 132 | 133 | return $currency; 134 | } 135 | 136 | protected function buildCategory(\SimpleXMLElement $xml) 137 | { 138 | $id = (string)$xml['id']; 139 | 140 | $parents[$id] = (string)$xml['parentId']; 141 | 142 | $category = $this->factory->createCategory(); 143 | 144 | $category 145 | ->setId($id) 146 | ->setName((string)$xml) 147 | ; 148 | 149 | return $category; 150 | } 151 | 152 | protected function buildOffer(\SimpleXMLElement $xml) 153 | { 154 | $type = (string)$xml['type']; 155 | 156 | if (!$type) { 157 | $type = 'vendor.model'; 158 | } 159 | 160 | $offer = $this->factory->createOffer($type); 161 | foreach ($xml->attributes() as $key => $value) { 162 | $offer->setAttribute($key, (string)$value); 163 | } 164 | 165 | $offer 166 | ->setId((string)$xml['id']) 167 | ->setAvailable(((string)$xml['available']) == 'true' ? true : false) 168 | ->setType($type) 169 | ->setXml($xml) 170 | ; 171 | 172 | foreach ($xml->param as $param) { 173 | $offer->addParam($this->createParam($param)); 174 | } 175 | 176 | foreach ($xml as $field => $value) { 177 | foreach (['add', 'set'] as $method) { 178 | $method .= $this->camelize($field); 179 | if (!in_array($field, ['param']) && method_exists($offer, $method)) { 180 | call_user_func([$offer, $method], count($value->children()) ? $value : (string)$value); 181 | break; 182 | } 183 | } 184 | } 185 | 186 | $currencyId = Currency::normalize((string)$xml->currencyId); 187 | 188 | if ($this->getShop()->getCurrency($currencyId)) { 189 | $offer->setCurrency($this->getShop()->getCurrency($currencyId)); 190 | } 191 | 192 | $categoryId = (string)$xml->categoryId; 193 | 194 | if ($this->getShop()->getCategory($categoryId)) { 195 | $offer->setCategory($this->getShop()->getCategory($categoryId)); 196 | } 197 | return $offer; 198 | } 199 | 200 | private function camelize($field) 201 | { 202 | return strtr(ucwords(strtr($field, array('_' => ' ', '.' => '_ '))), array(' ' => '')); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/Offer/Offer.php: -------------------------------------------------------------------------------- 1 | id; 74 | } 75 | 76 | public function setId($id) 77 | { 78 | $this->id = $id; 79 | return $this; 80 | } 81 | 82 | public function getType() 83 | { 84 | return $this->type; 85 | } 86 | 87 | public function setType($type) 88 | { 89 | $this->type = (string)$type; 90 | return $this; 91 | } 92 | 93 | public function getName() 94 | { 95 | return $this->name; 96 | } 97 | 98 | public function setName($name) 99 | { 100 | $this->name = (string)$name; 101 | return $this; 102 | } 103 | 104 | public function getDescription() 105 | { 106 | return $this->description; 107 | } 108 | 109 | public function setDescription($description) 110 | { 111 | $this->description = (string)$description; 112 | return $this; 113 | } 114 | 115 | public function isAvailable() 116 | { 117 | return $this->available; 118 | } 119 | 120 | public function setAvailable($available) 121 | { 122 | $this->available = (bool)$available; 123 | return $this; 124 | } 125 | 126 | public function getUrl() 127 | { 128 | return $this->url; 129 | } 130 | 131 | public function setUrl($url) 132 | { 133 | $this->url = (string)$url; 134 | return $this; 135 | } 136 | 137 | public function getPrice() 138 | { 139 | return $this->price; 140 | } 141 | 142 | public function setPrice($price) 143 | { 144 | $this->price = (float)$price; 145 | return $this; 146 | } 147 | 148 | public function getCurrency() 149 | { 150 | return $this->currency; 151 | } 152 | 153 | public function setCurrency($currency) 154 | { 155 | $this->currency = $currency; 156 | return $this; 157 | } 158 | 159 | public function getCategory() 160 | { 161 | return $this->category; 162 | } 163 | 164 | public function setCategory($category) 165 | { 166 | $this->category = $category; 167 | return $this; 168 | } 169 | 170 | public function getShop() 171 | { 172 | return $this->shop; 173 | } 174 | 175 | public function setShop($shop) 176 | { 177 | $this->shop = $shop; 178 | return $this; 179 | } 180 | 181 | public function getPictures() 182 | { 183 | return $this->pictures; 184 | } 185 | 186 | public function setPictures($pictures) 187 | { 188 | $this->pictures = []; 189 | if (is_array($pictures) || $pictures instanceof \Traversable) { 190 | foreach ($pictures as $picture) { 191 | $this->addPicture($picture); 192 | } 193 | } 194 | 195 | return $this; 196 | } 197 | 198 | public function addPicture($picture) 199 | { 200 | $this->pictures[] = (string)$picture; 201 | return $this; 202 | } 203 | 204 | public function getTypePrefix() 205 | { 206 | return $this->typePrefix; 207 | } 208 | 209 | public function setTypePrefix($typePrefix) 210 | { 211 | $this->typePrefix = $typePrefix; 212 | 213 | return $this; 214 | } 215 | 216 | public function getAttributes() 217 | { 218 | return $this->attributes; 219 | } 220 | 221 | public function setAttributes($attributes) 222 | { 223 | $this->attributes = $attributes; 224 | 225 | return $this; 226 | } 227 | 228 | public function setAttribute($key, $param) 229 | { 230 | $this->attributes[$key] = $param; 231 | return $this; 232 | } 233 | 234 | public function hasAttribute($key) 235 | { 236 | return isset($this->attributes[$key]); 237 | } 238 | 239 | public function getAttribute($key, $defaultValue = null) 240 | { 241 | return $this->hasAttribute($key) ? $this->attributes[$key] : $defaultValue; 242 | } 243 | 244 | public function getXml() 245 | { 246 | return $this->xml; 247 | } 248 | 249 | public function setXml($xml) 250 | { 251 | $this->xml = $xml; 252 | 253 | return $this; 254 | } 255 | 256 | public function getParam($name) 257 | { 258 | $name = mb_strtolower($name, 'UTF-8'); 259 | return isset($this->params[$name]) ? $this->params[$name] : null; 260 | } 261 | 262 | 263 | public function getParams() 264 | { 265 | return $this->params; 266 | } 267 | 268 | public function addParam(Param $param) 269 | { 270 | $name = mb_strtolower($param->getName(), 'UTF-8'); 271 | $this->params[$name] = $param; 272 | 273 | return $this; 274 | } 275 | 276 | public function setParams($params) 277 | { 278 | $this->params = []; 279 | if (is_array($params) || $params instanceof \Traversable) { 280 | foreach ($params as $param) { 281 | $this->addParam($param); 282 | } 283 | } 284 | 285 | return $this; 286 | } 287 | 288 | public function getOldPrice() 289 | { 290 | return $this->oldPrice; 291 | } 292 | 293 | public function setOldPrice($oldPrice) 294 | { 295 | $this->oldPrice = $oldPrice; 296 | 297 | return $this; 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /Tests/Fixtures/yml.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Magazin 6 | Magazin 7 | http://www.magazin.ru/ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Оргтехника 17 | Принтеры 18 | Струйные принтеры 19 | Лазерные принтеры 20 | 21 | Фототехника 22 | Фотоаппараты 23 | Объективы 24 | 25 | Книги 26 | Детективы 27 | Художественная литература 28 | Учебная литература 29 | Детская литература 30 | 31 | Музыка и видеофильмы 32 | Музыка 33 | Видеофильмы 34 | 35 | Путешествия 36 | Туры 37 | Авиабилеты 38 | 39 | Билеты на мероприятия 40 | 41 | 42 | 43 | 44 | http://magazin.ru/product_page.asp?pid=14344 45 | 15000 46 | 25000 47 | RUR 48 | 101 49 | http://magazin.ru/img/device14344.jpg 50 | true 51 | 300 52 | Принтер 53 | НP 54 | Q7533A 55 | Color LaserJet 3000 56 | A4, 64Mb, 600x600 dpi, USB 2.0, 29стр/мин ч/б / 15стр/мин цв, лотки на 100л и 250л, плотность до 175г/м, до 60000 стр/месяц 57 | true 58 | Япония 59 | 100 60 | 3 61 | 62 | 63 | 64 | http://magazin.ru/product_page.asp?pid=14345 65 | 100 66 | RUR 67 | 13 68 | http://magazin.ru/img/book14345.jpg 69 | true 70 | 300 71 | Александра Маринина 72 | Все не так. В 2 томах. Том 1 73 | ЭКСМО - Пресс 74 | А. Маринина - королева детектива 75 | 2009 76 | 978-5-699-23647-3 77 | 2 78 | 1 79 | rus 80 | 70x90/32 81 | 288 82 | Все прекрасно в большом патриархальном семействе Руденко. Но — увы! — впечатление это обманчиво: каждого из многочисленных представителей семьи обуревают свои потаенные страсти и запретные желания. 83 | false 84 | 85 | 86 | 87 | http://magazin.ru/product_page.asp?pid=14346 88 | 200 89 | RUR 90 | 14 91 | http://magazin.ru/img/book14346.jpg 92 | Владимир Кунин 93 | Иваnов и Rабинович, или Аj'гоу ту 'Хаjфа! 94 | 1С-Паблишинг, Союз 95 | 2009 96 | 978-5-9677-0757-5 97 | ru 98 | Николай Фоменко 99 | начитана 100 | CD 101 | mp3 102 | 45m23s 103 | Перу Владимира Кунина принадлежат десятки сценариев к кинофильмам, серия книг про КЫСЮ и многое, многое другое. 104 | true 105 | 106 | 107 | 108 | http://magazin.ru/product_page.asp?pid=14347 109 | 450 110 | RUR 111 | 17 112 | http://magazin.ru/img/cd14347.jpg 113 | true 114 | Pink Floyd 115 | Dark Side Of The Moon, Platinum Disc 116 | 1999 117 | CD 118 | Dark Side Of The Moon, поставивший мир на уши невиданным сочетанием звуков, — это всего-навсего девять треков, и даже не все они писались специально для альбома. Порывшись по сусекам, участники Pink Floyd мудро сделали новое из хорошо забытого старого — песен, которые почему-либо не пошли в дело или остались незаконченными. Одним из источников вдохновения стали саундтреки для кинофильмов, которые группа производила в больших количествах. 119 | 120 | 121 | 122 | http://magazin.ru/product_page.asp?pid=14348 123 | 400 124 | RUR 125 | 18 126 | http://magazin.ru/img/dvd14348.jpg 127 | true 128 | Свадьба Мюриэл 129 | 1999 130 | DVD 131 | Тони Колетт (Toni Collette), Рэйчел Грифитс (Rachel Griffiths) 132 | П Дж Хоген 133 | Muriel's wedding 134 | Австралия 135 | Гадкий утенок из провинциального городка покидает свое гнездо, и в компании своей подруги отправляется искать веселой жизни в большой и загадочный город. Фильм о мечтах и реальности, дружбе и юности молодой девушки, приключения которой повторяют судьбы Золушки и героини Джулии Робертс из ставшего классикой фильма Красотка... 136 | 137 | 138 | 139 | http://magazin.ru/product_page.asp?pid=14349 140 | 30000 141 | RUR 142 | 19 143 | http://magazin.ru/img/travel14349.jpg 144 | false 145 | 300 146 | Африка 147 | Египет 148 | Хургада 149 | 7 150 | 01/06/2014 151 | 08/06/2014 152 | Hilton 153 | 5***** 154 | SNG 155 | ALL 156 | авиаперелет, трансфер, проживание, питание, страховка 157 | Авиа 158 | Отдых в Египте. 159 | 160 | 161 | 162 | http://magazin.ru/product_page.asp?pid=14350 163 | 5000 164 | RUR 165 | 6 166 | http://magazin.ru/img/tickets14350.jpg 167 | true 168 | 300 169 | Дмитрий Хворостовский и Национальный филармонический оркестр России. Дирижер — Владимир Спиваков. 170 | Московский международный Дом музыки 171 | Большой зал 172 | Партер р. 1-5 173 | 2014-12-31T19:00 174 | 0 175 | 0 176 | Концерт Дмитрия Хворостовского и Национального филармонического оркестра России под управлением Владимира Спивакова. 177 | 178 | 179 | 180 | 181 | 182 | --------------------------------------------------------------------------------