Completed
Push — master ( fb77e4...5da68f )
by Thomas
17:56 queued 05:16
created

src/FluentDOM/Loader/Text/CSV.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Load a CSV file
4
 *
5
 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
6
 * @copyright Copyright (c) 2009-2016 Bastian Feder, Thomas Weinert
7
 */
8
9
namespace FluentDOM\Loader\Text {
10
11
  use FluentDOM\Document;
12
  use FluentDOM\DocumentFragment;
13
  use FluentDOM\Element;
14
  use FluentDOM\Loadable;
15
  use FluentDOM\Loader\Options;
16
  use FluentDOM\Loader\Supports;
17
  use FluentDOM\QualifiedName;
18
  use FluentDOM\Loader\Result;
19
20
  /**
21
   * Load a CSV file
22
   */
23
  class CSV implements Loadable {
24
25
    use Supports;
26
27
    const XMLNS = 'urn:carica-json-dom.2013';
28
    const DEFAULT_QNAME = '_';
29
30
    private $_delimiter = ',';
31
    private $_enclosure = '"';
32
    private $_escape = '\\';
33
34
    /**
35
     * @return string[]
36
     */
37 13
    public function getSupported() {
38 13
      return ['text/csv'];
39
    }
40
41
    /**
42
     * @see Loadable::load
43
     * @param mixed $source
44
     * @param string $contentType
45
     * @param array|\Traversable|Options $options
46
     * @return Document|Result|NULL
47
     */
48 8
    public function load($source, $contentType, $options = []) {
49 8
      $options = $this->getOptions($options);
50 8
      $hasHeaderLine = isset($options['HEADER']) ? (bool)$options['HEADER'] : !isset($options['FIELDS']);
51 8
      $this->configure($options);
52 8
      if ($this->supports($contentType) && ($lines = $this->getLines($source, $options))) {
53 7
        $document = new Document('1.0', 'UTF-8');
54 7
        $document->appendChild($list = $document->createElementNS(self::XMLNS, 'json:json'));
55 7
        $list->setAttributeNS(self::XMLNS, 'json:type', 'array');
56 7
        $this->appendLines($list, $lines, $hasHeaderLine, isset($options['FIELDS']) ? $options['FIELDS'] : NULL);
57 7
        return $document;
58
      }
59 1
      return NULL;
60
    }
61
62
    /**
63
     * @see Loadable::loadFragment
64
     *
65
     * @param string $source
66
     * @param string $contentType
67
     * @param array|\Traversable|Options $options
68
     * @return DocumentFragment|NULL
69
     */
70 3
    public function loadFragment($source, $contentType, $options = []) {
71 3
      $options = $this->getOptions($options);
72 3
      $options[Options::ALLOW_FILE] = FALSE;
73 3
      $hasHeaderLine = isset($options['FIELDS']) ? FALSE : (isset($options['HEADER']) && $options['HEADER']);
74 3
      $this->configure($options);
75 3
      if ($this->supports($contentType) && ($lines = $this->getLines($source, $options))) {
76 2
        $document = new Document('1.0', 'UTF-8');
77 2
        $fragment = $document->createDocumentFragment();
78 2
        $this->appendLines($fragment, $lines, $hasHeaderLine, isset($options['FIELDS']) ? $options['FIELDS'] : NULL);
79 2
        return $fragment;
80
      }
81 1
      return NULL;
82
    }
83
84
    /**
85
     * Append the provided lines to the parent.
86
     *
87
     * @param \DOMNode $parent
88
     * @param $lines
89
     * @param $hasHeaderLine
90
     * @param array $fields
91
     */
92 9
    private function appendLines(\DOMNode $parent, $lines, $hasHeaderLine, array $fields = NULL) {
93 9
      $document = $parent instanceof \DOMDocument ? $parent : $parent->ownerDocument;
94 9
      $headers = NULL;
95 9
      foreach ($lines as $line) {
96 9
        if ($headers === NULL) {
97 9
          $headers = $this->getHeaders(
98 9
            $line, $hasHeaderLine, $fields
99 9
          );
100 9
          if ($hasHeaderLine) {
101 5
            continue;
102
          }
103 4
        }
104
        /** @var Element $node */
105 9
        $parent->appendChild($node = $document->createElement(self::DEFAULT_QNAME));
106 9
        foreach ($line as $index => $field) {
107 9
          if (isset($headers[$index])) {
108 9
            $this->appendField($node, $headers[$index], $field);
1 ignored issue
show
$node of type object<DOMElement> is not a sub-type of object<FluentDOM\Element>. It seems like you assume a child class of the class DOMElement to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
109 9
          }
110 9
        }
111 9
      }
112 9
    }
113
114
    /**
115
     * @param Element $parent
116
     * @param string $name
117
     * @param string $value
118
     */
119 9
    private function appendField(Element $parent, $name, $value) {
120 9
      $qname = QualifiedName::normalizeString($name, self::DEFAULT_QNAME);
121 9
      $child = $parent->appendElement($qname, $value);
122 9
      if ($qname !== $name) {
123 3
        $child->setAttributeNS(self::XMLNS, 'json:name', $name);
124 3
      }
125 9
    }
126
127
    /**
128
     * @param array $line
129
     * @param bool $hasHeaderLine
130
     * @param array|NULL $fields
131
     * @return array
132
     */
133 9
    private function getHeaders(array $line, $hasHeaderLine, $fields = NULL) {
134 9
      if (is_array($fields)) {
135 2
        $headers = [];
136 2
        foreach ($line as $index => $field) {
137 2
          $key = $hasHeaderLine ? $field : $index;
138 2
          $headers[$index] = isset($fields[$key]) ? $fields[$key] : FALSE;
139 7
        }
140 2
        return $headers;
141 9
      } elseif ($hasHeaderLine) {
142 5
        return $line;
143
      } else {
144 2
        return array_keys($line);
145
      }
146
    }
147
148 10
    private function getLines($source, Options $options) {
149 10
      $result = null;
150 10
      if (is_string($source)) {
151 7
        $options->isAllowed($sourceType = $options->getSourceType($source));
152
        switch ($sourceType) {
153 7
        case Options::IS_FILE :
154 1
          $result = new \SplFileObject($source);
155 1
          break;
156 6
        default :
157 6
          $result = new \SplFileObject('data://text/csv;base64,'.base64_encode($source));
158 6
        }
159 7
        $result->setFlags(\SplFileObject::READ_CSV);
160 7
        $result->setCsvControl(
161 7
          $this->_delimiter,
162 7
          $this->_enclosure,
163 7
          $this->_escape
164 7
        );
165 10
      } elseif (is_array($source)) {
166 1
        $result = new \ArrayIterator($source);
167 3
      } elseif ($source instanceof \Traversable) {
168 1
        $result = $source;
169 1
      }
170 10
      return (empty($result)) ? NULL : $result;
171
    }
172
173
    /**
174
     * @param array|\Traversable|Options $options
175
     */
176 11
    private function configure($options) {
177 11
      $this->_delimiter = isset($options['DELIMITER']) ? $options['DELIMITER'] : $this->_delimiter;
178 11
      $this->_enclosure = isset($options['ENCLOSURE']) ? $options['ENCLOSURE'] : $this->_enclosure;
179 11
      $this->_escape = isset($options['ESCAPE']) ? $options['ESCAPE'] : $this->_escape;
180 11
    }
181
182
    /**
183
     * @param array|\Traversable|Options $options
184
     * @return Options
185
     */
186 11
    public function getOptions($options) {
187 11
      $result = new Options(
188 11
        $options,
189
        [
190 6
          Options::CB_IDENTIFY_STRING_SOURCE => function($source) {
191 6
            return (is_string($source) && (FALSE !== strpos($source, "\n")));
192
          }
193 11
        ]
194 11
      );
195 11
      return $result;
196
    }
197
  }
198
199
}