XMLForm   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 170
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 74
c 2
b 0
f 1
dl 0
loc 170
rs 10
wmc 28

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getErrorMsg() 0 3 1
A getFormatedXMLError() 0 21 4
A setPlainError() 0 3 1
A setSchemaValidation() 0 3 1
B createChildElements() 0 26 10
B loadXML() 0 40 11
1
<?php
2
declare(strict_types=1);
3
4
namespace SKien\Formgenerator;
5
6
/**
7
 * An instance of this class is the starting point of each form based on XML definition.
8
 *
9
 * @see FormGenerator
10
 * @package Formgenerator
11
 * @author Stefanius <[email protected]>
12
 * @copyright MIT License - see the LICENSE file for details
13
 */
14
class XMLForm extends FormGenerator
15
{
16
    /** no xml/xsd error occurred    */
17
    const E_OK = 0;
18
    /** the specified file does not exist   */
19
    const E_FILE_NOT_EXIST = 1;
20
    /** any xml syntax error    */
21
    const E_XML_ERROR = 2;
22
    /** error while xsd schema validation   */
23
    const E_XSD_ERROR = 3;
24
    /** the root element &lt;FormGenerator&gt; is missing   */
25
    const E_MISSING_ROOT = 4;
26
    /** no &lt;Form&gt; element found   */
27
    const E_MISSING_FORM = 5;
28
    /** an unknown form element found   */
29
    const E_UNKNOWN_FORM_ELEMENT = 6;
30
31
    /** @internal the XSD schema to validate against     */
32
    const XML_SCHEMA = 'FormGenerator.xsd';
33
34
    /** @var string error message     */
35
    protected string $strErrorMsg = '';
36
    /** @var bool error message as plain text (\n instead of &lt;br/&gt;) */
37
    protected bool $bPlainError = false;
38
    /** @var bool check xml file against the FormGenerator XSD schema */
39
    protected bool $bSchemaValidate = true;
40
41
    /**
42
     * no constructor - always use the constructor of the parent!
43
     */
44
45
    /**
46
     * Load form from the given XML file.
47
     * @param string $strXMLFile
48
     * @return int
49
     */
50
    public function loadXML(string $strXMLFile) : int
51
    {
52
        if (!file_exists($strXMLFile)) {
53
            // if file not exist, there is nothing more to do...
54
            $this->strErrorMsg = 'Missing form file: ' . $strXMLFile;
55
            return self::E_FILE_NOT_EXIST;
56
        }
57
        // to get more detailed information about XML errors and XML schema validation
58
        libxml_use_internal_errors(true);
59
        $iResult = self::E_XML_ERROR;
60
        $oXMLForm = new \DOMDocument();
61
        if ($oXMLForm->load($strXMLFile)) {
62
            // the XML schema is allways expected in the same directory as the XML file itself
63
            $strXSDFile = (string)pathinfo($strXMLFile, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR . self::XML_SCHEMA;
64
            $oRoot = $oXMLForm->documentElement;
65
            if ($oRoot !== null)
66
            {
67
                if ($this->bSchemaValidate && !$oXMLForm->schemaValidate($strXSDFile)) {
68
                    $iResult = self::E_XSD_ERROR;
69
                } elseif ($oRoot->nodeName != 'FormGenerator') {
70
                    $this->strErrorMsg = 'Missing document root &lt;FormGenerator&gt; in form file: ' . $strXMLFile;
71
                    $iResult = self::E_MISSING_ROOT;
72
                } elseif (($oForm = $this->getXMLChild($oRoot, 'Form')) === null) {
73
                    $this->strErrorMsg = 'Missing form elemnt &lt;Form&gt; as first child of the root: ' . $strXMLFile;
74
                    $iResult = self::E_MISSING_FORM;
75
                } else {
76
                    // First we read some general infos for the form
77
                    $this->readAdditionalXML($oForm);
78
79
                    // and iterate recursive through the child elements
80
                    $iResult = $this->createChildElements($oForm, $this);
81
                }
82
            }
83
        }
84
        if ($iResult != self::E_OK && strlen($this->strErrorMsg) == 0) {
85
            $strErrorMsg = ($iResult != self::E_XSD_ERROR ? 'XML error' : 'XSD Schema validation error');
86
            $this->strErrorMsg .= $this->getFormatedXMLError($strErrorMsg, $this->bPlainError);
87
        }
88
        libxml_clear_errors();
89
        return $iResult;
90
    }
91
92
    /**
93
     * Iterate through all childs of the given parent node and create the according element.
94
     * This method cals itself recursive for all collection elements.
95
     * @param \DOMElement $oXMLParent the DOM Element containing the infos for the formelement to create
96
     * @param FormCollection $oFormParent the parent element in the form
97
     * @return int
98
     */
99
    protected function createChildElements(\DOMElement $oXMLParent, FormCollection $oFormParent) : int
100
    {
101
        $iResult = self::E_OK;
102
        if (!$oXMLParent->hasChildNodes()) {
103
            return $iResult;
104
        }
105
        foreach ($oXMLParent->childNodes as $oXMLChild) {
106
            if (strtolower($oXMLChild->nodeName) == '#text' || strtolower($oXMLChild->nodeName) == '#comment' || !($oXMLChild instanceof \DOMElement)) {
107
                continue;
108
            }
109
            $strClassname = __NAMESPACE__ . '\Form' . $oXMLChild->nodeName;
110
            if (class_exists($strClassname) && is_subclass_of($strClassname, __NAMESPACE__ . '\FormElement')) {
111
                $oFormElement = $strClassname::fromXML($oXMLChild, $oFormParent);
112
                // recursive call for collection element (div, fieldset, line)
113
                if ($oFormElement instanceof FormCollection) {
114
                    $iResult = $this->createChildElements($oXMLChild, $oFormElement);
115
                }
116
            } else {
117
                $this->strErrorMsg = 'Unknown form element: ' . $oXMLChild->nodeName;
118
                $iResult = self::E_UNKNOWN_FORM_ELEMENT;
119
            }
120
            if ($iResult != self::E_OK) {
121
                break;
122
            }
123
        }
124
        return $iResult;
125
    }
126
127
    /**
128
     * Get message to error occured.
129
     * May contain multiple lines in case of any XML formating errors.
130
     * @return string
131
     */
132
    public function getErrorMsg() : string
133
    {
134
        return $this->strErrorMsg;
135
    }
136
137
    /**
138
     * Format error message as plain text (\n instead of <br/> for multiline errors)
139
     * @param bool $bPlainError
140
     */
141
    public function setPlainError(bool $bPlainError) : void
142
    {
143
        $this->bPlainError = $bPlainError;
144
    }
145
146
    /**
147
     * enable/disable the schema validation (default set to true)
148
     * @param bool $bSchemaValidate
149
     */
150
    public function setSchemaValidation(bool $bSchemaValidate) : void
151
    {
152
        $this->bSchemaValidate = $bSchemaValidate;
153
    }
154
155
    /**
156
     * Get formated list of detailed XML error.
157
     * this method only works, if <i><b>libxml_use_internal_errors(true);</b></i> is called
158
     * before parsing the xml and/or validating the xml against XSD file.
159
     * @param string $strError
160
     * @param bool $bPlainText
161
     * @return string
162
     */
163
    protected function getFormatedXMLError(string $strError, bool $bPlainText) : string
164
    {
165
        $strErrorMsg = '';
166
        $errors = libxml_get_errors();
167
        $aLevel = [LIBXML_ERR_WARNING => 'Warning ', LIBXML_ERR_ERROR => 'Error ', LIBXML_ERR_FATAL => 'Fatal Error '];
168
        if ($bPlainText) {
169
            $strErrorMsg = $strError . ': ';
170
            foreach ($errors as $error) {
171
                $strErrorMsg .= PHP_EOL . $aLevel[$error->level] . $error->code;
172
                $strErrorMsg .= ' (Line ' . $error->line . ', Col ' . $error->column . ') ';
173
                $strErrorMsg .= PHP_EOL . trim($error->message);
174
            }
175
        } else {
176
            $strErrorMsg = '<h2>' . $strError . '</h2>';
177
            foreach ($errors as $error) {
178
                $strErrorMsg .= '<h3>' . $aLevel[$error->level] . $error->code . '</h3>';
179
                $strErrorMsg .= '<ul><li>Line: ' . $error->line . '</li><li>Col: ' . $error->column . '</li></ul>';
180
                $strErrorMsg .= '<p>' . trim($error->message) . '</p>';
181
            }
182
        }
183
        return $strErrorMsg;
184
    }
185
}
186