1 /*
2 *             Copyright Lodovico Giaretta 2016 - .
3 *  Distributed under the Boost Software License, Version 1.0.
4 *      (See accompanying file LICENSE_1_0.txt or copy at
5 *            http://www.boost.org/LICENSE_1_0.txt)
6 */
7 
8 // TODO: write an in-depth explanation of this module, how to create validations,
9 // how validations should behave, etc...
10 
11 /++
12 +   Authors:
13 +   Lodovico Giaretta
14 +   László Szerémi
15 +
16 +   License:
17 +   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
18 +
19 +   Copyright:
20 +   Copyright Lodovico Giaretta 2016 --
21 +/
22 
23 module newxml.validation;
24 
25 import newxml.interfaces;
26 
27 /**
28 *   Checks whether a character can appear in an XML 1.0 document.
29 */
30 pure nothrow @nogc @safe bool isValidXMLCharacter10(dchar c)
31 {
32     return c == '\r' || c == '\n' || c == '\t'
33         || (0x20 <= c && c <= 0xD7FF)
34         || (0xE000 <= c && c <= 0xFFFD)
35         || (0x10000 <= c && c <= 0x10FFFF);
36 }
37 
38 /**
39 *   Checks whether a character can appear in an XML 1.1 document.
40 */
41 pure nothrow @nogc @safe bool isValidXMLCharacter11(dchar c)
42 {
43     return (1 <= c && c <= 0xD7FF)
44         || (0xE000 <= c && c <= 0xFFFD)
45         || (0x10000 <= c && c <= 0x10FFFF);
46 }
47 /** 
48  * Checks whether a text contains invalid characters for an XML 1.0 document.
49  * Params:
50  *   input = The text to test for.
51  * Returns: true if text doesn't contain any invalid characters.
52  */
53 pure nothrow @nogc @safe bool isValidXMLText10(T)(T[] input)
54 {
55     foreach (elem; input) 
56     {
57         if (!isValidXMLCharacter10(elem)) return false;
58     }
59     return true;
60 }
61 /** 
62  * Checks whether a text contains invalid characters for an XML 1.1 document.
63  * Params:
64  *   input = The text to test for.
65  * Returns: true if text doesn't contain any invalid characters.
66  */
67 pure nothrow @nogc @safe bool isValidXMLText11(T)(T[] input)
68 {
69     foreach (elem; input) 
70     {
71         if (!isValidXMLCharacter11(elem)) return false;
72     }
73     return true;
74 }
75 
76 /**
77 *   Checks whether a character can start an XML name (tag name or attribute name).
78 */
79 pure nothrow @nogc @safe bool isValidXMLNameStart(dchar c)
80 {
81     return c == ':'
82         || ('A' <= c && c <= 'Z')
83         || c == '_'
84         || ('a' <= c && c <= 'z')
85         || (0xC0 <= c && c <= 0x2FF && c != 0xD7 && c != 0xF7)
86         || (0x370 <= c && c <= 0x1FFF && c != 0x37E)
87         || c == 0x200C
88         || c == 0x200D
89         || (0x2070 <= c && c <= 0x218F)
90         || (0x2C00 <= c && c <= 0x2FEF)
91         || (0x3001 <= c && c <= 0xD7FF)
92         || (0xF900 <= c && c <= 0xFDCF)
93         || (0xFDF0 <= c && c <= 0xEFFFF && c != 0xFFFE && c != 0xFFFF);
94 }
95 
96 /**
97 *   Checks whether a character can appear inside an XML name (tag name or attribute name).
98 */
99 pure nothrow @nogc @safe bool isValidXMLNameChar(dchar c)
100 {
101     return isValidXMLNameStart(c)
102         || c == '-'
103         || c == '.'
104         || ('0' <= c && c <= '9')
105         || c == 0xB7
106         || (0x300 <= c && c <= 0x36F)
107         || (0x203F <= c && c <= 2040);
108 }
109 
110 /** 
111  * Checks whether a name is a valid XML name or not.
112  * Params:
113  *   input = The input string.
114  * Returns: True if XML name is valid.
115  */
116 pure nothrow @nogc @safe bool isValidXMLName(T)(T[] input) {
117     if (!input.length) return false;
118     if (!isValidXMLNameStart(input[0])) return false;
119     for (sizediff_t i = 1 ; i < input.length; i++)
120         if (!isValidXMLNameChar(input[i])) return false;
121     return true;
122 }
123 
124 /**
125 *   Checks whether a character can appear in an XML public ID.
126 */
127 pure nothrow @nogc @safe bool isValidXMLPublicIdCharacter(dchar c)
128 {
129     import std.string: indexOf;
130     return c == ' '
131         || c == '\n'
132         || c == '\r'
133         || ('a' <= c && c <= 'z')
134         || ('A' <= c && c <= 'Z')
135         || ('0' <= c && c <= '9')
136         || "-'()+,./:=?;!*#@$_%".indexOf(c) != -1;
137 }
138 
139 unittest
140 {
141     assert(isValidXMLName("foo"));
142     assert(isValidXMLName("bar"));
143     assert(!isValidXMLName(".foo"));
144     assert(isValidXMLName("foo:bar"));
145 }
146 
147 /** 
148  * A simple document validation stack.
149  * Node names on every non-empty starting nodes are pushed here, then on every ending node the top is popped then 
150  * compared with the name.
151  */
152 struct ValidationStack(StringType)
153 {
154     StringType[] stack;
155     /** 
156      * Pushes a name to the top.
157      */
158     void push(StringType input) @safe pure nothrow
159     {
160         stack ~= input;
161     }
162     /** 
163      * Pops a name from the top, then compared with the input.
164      * Params:
165      *   input = the string that is being compared with the input.
166      * Returns: True if a string could been removed from the stack and it's identical with the input, false otherwise.
167      */
168     bool pop(StringType input) @safe pure nothrow
169     {
170         if (stack.length)
171         {
172             StringType top = stack[$-1];
173             stack = stack[0..$-1];
174             return top == input;
175         }
176         else 
177         {
178             return false;
179         }
180     }
181 }