Passed
Push — master ( eeab5f...5e4f21 )
by Daniele
02:41
created

FluidXml::addCdata()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 1
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2
Metric Value
dl 0
loc 1
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 1
nc 1
nop 1
crap 2
1
<?php
2
3
namespace FluidXml;
4
5
/**
6
 * @method array array()
7
 * @method FluidXml namespace(...$arguments)
8
 */
9
class FluidXml implements FluidInterface
10
{
11
        use FluidAliasesTrait,
12
            FluidSaveTrait,
13
            NewableTrait,
14
            ReservedCallTrait,          // For compatibility with PHP 5.6.
15
            ReservedCallStaticTrait;    // For compatibility with PHP 5.6.
16
17
        const ROOT_NODE = 'doc';
18
19
        private $defaults = [ 'root'       => self::ROOT_NODE,
20
                              'version'    => '1.0',
21
                              'encoding'   => 'UTF-8',
22
                              'stylesheet' => null ];
23
24
        private $document;
25
        private $handler;
26
        private $context;
27
        private $contextEl;
28
29 1
        public static function load($document)
30
        {
31 1
                $file     = $document;
32 1
                $document = \file_get_contents($file);
33
34
                // file_get_contents() returns false in case of error.
35 1
                if (! $document) {
36 1
                        throw new \Exception("File '$file' not accessible.");
37
                }
38
39 1
                return (new FluidXml(null))->addChild($document);
40
        }
41
42 1
        public function __construct(...$arguments)
43
        {
44
                // First, we parse the arguments detecting the options provided.
45
                // This options are needed to build the DOM, add the stylesheet
46
                // and to create the document root/structure.
47 1
                $options = $this->mergeOptions($arguments);
48
49
                // Having the options set, we can build the FluidDocument model
50
                // which incapsulates the DOM and the corresponding XPath instance.
51 1
                $document = new FluidDocument();
52 1
                $document->dom   = $this->newDom($options);
53 1
                $document->xpath = new \DOMXPath($document->dom);
54
55
                // After the FluidDocument model creation, we can proceed to build
56
                // the FluidInsertionHandler which requires the model to perform
57
                // its logics.
58 1
                $handler = new FluidInsertionHandler($document);
59
60
                // Ok, it's time to let them beeing visible along the instance.
61 1
                $this->document = $document;
62 1
                $this->handler  = $handler;
63
64
                // Now, we can further populate the DOM with any stylesheet or child.
65 1
                $this->initStylesheet($options)
66 1
                     ->initRoot($options);
67 1
        }
68
69 1
        protected function mergeOptions(&$arguments)
70
        {
71 1
                $options = $this->defaults;
72
73 1
                if (\count($arguments) > 0) {
74
                        // The root option can be specified as first argument
75
                        // because it is the most common.
76 1
                        $options['root'] = $arguments[0];
77
                }
78
79 1
                if (\count($arguments) > 1) {
80
                        // Custom options can be specified only as second argument,
81
                        // to avoid confusion with array to XML construction style.
82 1
                        $options = \array_merge($options, $arguments[1]);
83
                }
84
85 1
                return $options;
86
        }
87
88 1
        private function newDom(&$options)
89
        {
90 1
                $dom = new \DOMDocument($options['version'], $options['encoding']);
91 1
                $dom->formatOutput       = true;
92 1
                $dom->preserveWhiteSpace = false;
93
94 1
                return $dom;
95
        }
96
97 1
        private function initStylesheet(&$options)
98
        {
99 1
                if (! empty($options['stylesheet'])) {
100
                        $attrs = 'type="text/xsl" '
101 1
                               . "encoding=\"{$options['encoding']}\" "
102 1
                               . 'indent="yes" '
103 1
                               . "href=\"{$options['stylesheet']}\"";
104
105 1
                        $stylesheet = new \DOMProcessingInstruction('xml-stylesheet', $attrs);
106
107 1
                        $this->addChild($stylesheet);
108
109
                        // Algorithm 2:
110
                        // Used in case the order of the stylesheet and root creation is reversed.
111
                        // $this->document->dom->insertBefore($stylesheet, $this->document->dom->documentElement);
112
                }
113
114 1
                return $this;
115
        }
116
117 1
        private function initRoot(&$options)
118
        {
119 1
                if (! empty($options['root'])) {
120 1
                        $this->appendSibling($options['root']);
121
                }
122
123 1
                return $this;
124
        }
125
126 1
        public function length()
127
        {
128 1
                return \count($this->array());
129
        }
130
131 1
        public function dom()
132
        {
133 1
                return $this->document->dom;
134
        }
135
136
        // This method should be called 'array',
137
        // but for compatibility with PHP 5.6
138
        // it is shadowed by the __call() method.
139 1
        public function array_()
140
        {
141 1
                $el = $this->document->dom->documentElement;
142
143 1
                if ($el === null) {
144 1
                        $el = $this->document->dom;
145
                }
146
147 1
                return [ $el ];
148
        }
149
150 1
        public function __toString()
151
        {
152 1
                return $this->xml();
153
        }
154
155 1
        public function xml($strip = false)
156
        {
157 1
                if ($strip) {
158 1
                        return FluidHelper::domdocumentToStringWithoutHeaders($this->document->dom);
159
                }
160
161 1
                return $this->document->dom->saveXML();
162
        }
163
164 1
        public function html($strip = false)
165
        {
166 1
                $header = "<!DOCTYPE html>\n";
167
168 1
                if ($strip) {
169 1
                        $header = '';
170
                }
171
172 1
                $html = FluidHelper::domdocumentToStringWithoutHeaders($this->document->dom, true);
173
174 1
                return "{$header}{$html}";
175
        }
176
177 1
        public function namespaces()
178
        {
179 1
                return $this->document->namespaces;
180
        }
181
182
        // This method should be called 'namespace',
183
        // but for compatibility with PHP 5.6
184
        // it is shadowed by the __call() method.
185 1
        protected function namespace_(...$arguments)
186
        {
187 1
                $namespaces = [];
188
189 1
                if (\is_string($arguments[0])) {
190 1
                        $args = [ $arguments[0], $arguments[1] ];
191
192 1
                        if (isset($arguments[2])) {
193 1
                                $args[] = $arguments[2];
194
                        }
195
196 1
                        $namespaces[] = new FluidNamespace(...$args);
197 1
                }  elseif (\is_array($arguments[0])) {
198 1
                        $namespaces = $arguments[0];
199
                } else {
200 1
                        $namespaces = $arguments;
201
                }
202
203 1
                foreach ($namespaces as $n) {
204 1
                        $this->document->namespaces[$n->id()] = $n;
205 1
                        $this->document->xpath->registerNamespace($n->id(), $n->uri());
206
                }
207
208 1
                return $this;
209
        }
210
211
        public function query(...$query)                   { return $this->context()->query(...$query); }
212
        public function times($times, callable $fn = null) { return $this->context()->times($times, $fn); }
213
        public function each(callable $fn)                 { return $this->context()->each($fn); }
214
        public function filter(callable $fn)               { return $this->context()->filter($fn); }
215
        public function setAttribute($name, $value = null) { $this->context()->setAttribute($name, $value); return $this; }
216
        public function setText($text)                     { $this->context()->setText($text);    return $this; }
217
        public function addText($text)                     { $this->context()->addText($text);    return $this; }
218
        public function setCdata($text)                    { $this->context()->setCdata($text);   return $this; }
219
        public function addCdata($text)                    { $this->context()->addCdata($text);   return $this; }
220
        public function setComment($text)                  { $this->context()->setComment($text); return $this; }
221
        public function addComment($text)                  { $this->context()->addComment($text); return $this; }
222
        public function remove(...$query)                  { $this->context()->remove(...$query); return $this; }
223
224 1
        public function addChild($child, ...$optionals)
225
        {
226
                return $this->chooseContext(function($cx) use ($child, &$optionals) {
227 1
                        return $cx->addChild($child, ...$optionals);
228 1
                });
229
        }
230
231 1
        public function prependSibling($sibling, ...$optionals)
232
        {
233
                return $this->chooseContext(function($cx) use ($sibling, &$optionals) {
234 1
                        return $cx->prependSibling($sibling, ...$optionals);
235 1
                });
236
        }
237
238
        public function appendSibling($sibling, ...$optionals)
239
        {
240 1
                return $this->chooseContext(function($cx) use ($sibling, &$optionals) {
241 1
                        return $cx->appendSibling($sibling, ...$optionals);
242 1
                });
243
        }
244
245 1
        protected function context()
246
        {
247 1
                $el = $this->document->dom->documentElement;
248
249 1
                if ($el === null) {
250
                        // Whether there is not a root node
251
                        // the DOMDocument is promoted as root node.
252 1
                        $el = $this->document->dom;
253
                }
254
255 1
                if ($this->context === null || $el !== $this->contextEl) {
256
                        // The user can prepend a root node to the current root node.
257
                        // In this case we have to update the context with the new first root node.
258 1
                        $this->context   = new FluidContext($this->document, $this->handler, $el);
259 1
                        $this->contextEl = $el;
260
                }
261
262 1
                return $this->context;
263
        }
264
265 1
        protected function chooseContext(\Closure $fn)
266
        {
267
                // If the user has requested ['root' => null] at construction time
268
                // 'context()' promotes DOMDocument as root node.
269
270 1
                $context     = $this->context();
271 1
                $new_context = $fn($context);
272
273 1
                if ($context !== $new_context) {
274
                        // If the two contextes are diffent, the user has requested
275
                        // a switch of the context and we have to return it.
276 1
                        return $new_context;
277
                }
278
279 1
                return $this;
280
        }
281
}
282