diff options
author | UbitUmarov | 2018-01-25 06:50:49 +0000 |
---|---|---|
committer | UbitUmarov | 2018-01-25 06:50:49 +0000 |
commit | 266eabcad493863de2ddb8430b7d4509e584dfb9 (patch) | |
tree | 7eddd1568f3757dc95baf1b7348fb918b8758858 /OpenSim | |
parent | change DataSnapeShoot init again. May still fail on multi regions instances (diff) | |
download | opensim-SC-266eabcad493863de2ddb8430b7d4509e584dfb9.zip opensim-SC-266eabcad493863de2ddb8430b7d4509e584dfb9.tar.gz opensim-SC-266eabcad493863de2ddb8430b7d4509e584dfb9.tar.bz2 opensim-SC-266eabcad493863de2ddb8430b7d4509e584dfb9.tar.xz |
add a low level LLSDxml encoder for cases where it makes no sense to use more heavy things like OSD, and use it on displaynames
Diffstat (limited to 'OpenSim')
-rw-r--r-- | OpenSim/Framework/LLSDxmlEncode.cs | 457 | ||||
-rw-r--r-- | OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs | 67 |
2 files changed, 490 insertions, 34 deletions
diff --git a/OpenSim/Framework/LLSDxmlEncode.cs b/OpenSim/Framework/LLSDxmlEncode.cs new file mode 100644 index 0000000..afa0214 --- /dev/null +++ b/OpenSim/Framework/LLSDxmlEncode.cs | |||
@@ -0,0 +1,457 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | // a class for low level LLSD encoding into a provided StringBuilder | ||
29 | // for cases where we already need to know the low level detail | ||
30 | // and so using something like OSD or even protbuf is just a pure waste | ||
31 | |||
32 | using System; | ||
33 | using System.Globalization; | ||
34 | using System.Text; | ||
35 | using OpenMetaverse; | ||
36 | |||
37 | namespace OpenSim.Framework | ||
38 | { | ||
39 | public static class LLSDxmlEncode | ||
40 | { | ||
41 | static readonly DateTime depoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | ||
42 | |||
43 | public static void AddStartHeader(StringBuilder sb, bool addxmlversion = false) | ||
44 | { | ||
45 | if(addxmlversion) | ||
46 | sb.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><llsd>"); // legacy llsd xml name still valid | ||
47 | else | ||
48 | sb.Append("<llsd>"); | ||
49 | } | ||
50 | |||
51 | public static void AddEndHeader(StringBuilder sb) | ||
52 | { | ||
53 | sb.Append("</llsd>"); | ||
54 | } | ||
55 | |||
56 | // map == a list of key value pairs | ||
57 | public static void AddStartMap(StringBuilder sb) | ||
58 | { | ||
59 | sb.Append("<map>"); | ||
60 | } | ||
61 | |||
62 | public static void AddEndMap(StringBuilder sb) | ||
63 | { | ||
64 | sb.Append("</map>"); | ||
65 | } | ||
66 | |||
67 | public static void AddEmpyMap(StringBuilder sb) | ||
68 | { | ||
69 | sb.Append("<map />"); | ||
70 | } | ||
71 | |||
72 | // array == a list values | ||
73 | public static void AddStartArray(StringBuilder sb) | ||
74 | { | ||
75 | sb.Append("<array>"); | ||
76 | } | ||
77 | |||
78 | public static void AddEndArray(StringBuilder sb) | ||
79 | { | ||
80 | sb.Append("</array>"); | ||
81 | } | ||
82 | |||
83 | public static void AddEmpyArray(StringBuilder sb) | ||
84 | { | ||
85 | sb.Append("<array />"); | ||
86 | } | ||
87 | |||
88 | // undefined or null | ||
89 | public static void AddUnknownElem(StringBuilder sb) | ||
90 | { | ||
91 | sb.Append("<undef />"); | ||
92 | } | ||
93 | |||
94 | public static void AddElem(bool e, StringBuilder sb) | ||
95 | { | ||
96 | if(e) | ||
97 | sb.Append("<boolean>1</boolean>"); | ||
98 | else | ||
99 | sb.Append("<boolean />"); | ||
100 | } | ||
101 | |||
102 | public static void AddElem(int e, StringBuilder sb) | ||
103 | { | ||
104 | if(e == 0) | ||
105 | sb.Append("<integer />"); | ||
106 | else | ||
107 | { | ||
108 | sb.Append("<integer>"); | ||
109 | sb.Append(e.ToString()); | ||
110 | sb.Append("</integer>"); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | public static void AddElem(float e, StringBuilder sb) | ||
115 | { | ||
116 | if(e == 0) | ||
117 | sb.Append("<real />"); | ||
118 | else | ||
119 | { | ||
120 | sb.Append("<real>"); | ||
121 | sb.Append(e.ToString(CultureInfo.InvariantCulture)); | ||
122 | sb.Append("</real>"); | ||
123 | } | ||
124 | } | ||
125 | |||
126 | public static void AddElem(double e, StringBuilder sb) | ||
127 | { | ||
128 | if(e == 0) | ||
129 | sb.Append("<real />"); | ||
130 | else | ||
131 | { | ||
132 | sb.Append("<real>"); | ||
133 | sb.Append(e.ToString(CultureInfo.InvariantCulture)); | ||
134 | sb.Append("</real>"); | ||
135 | } | ||
136 | } | ||
137 | |||
138 | public static void AddElem(UUID e, StringBuilder sb) | ||
139 | { | ||
140 | if(e == UUID.Zero) | ||
141 | sb.Append("<uuid />"); | ||
142 | else | ||
143 | { | ||
144 | sb.Append("<uuid>"); | ||
145 | EscapeToXML(e.ToString(), sb); | ||
146 | sb.Append("</uuid>"); | ||
147 | } | ||
148 | } | ||
149 | |||
150 | public static void AddElem(string e, StringBuilder sb) | ||
151 | { | ||
152 | if(String.IsNullOrEmpty(e)) | ||
153 | sb.Append("<string />"); | ||
154 | else | ||
155 | { | ||
156 | sb.Append("<string>"); | ||
157 | sb.Append(e.ToString()); | ||
158 | sb.Append("</string>"); | ||
159 | } | ||
160 | } | ||
161 | |||
162 | public static void AddRawElem(string e, StringBuilder sb) | ||
163 | { | ||
164 | if(String.IsNullOrEmpty(e)) | ||
165 | sb.Append("<string />"); | ||
166 | else | ||
167 | { | ||
168 | sb.Append("<string>"); | ||
169 | sb.Append(e); | ||
170 | sb.Append("</string>"); | ||
171 | } | ||
172 | } | ||
173 | |||
174 | public static void AddURIElem(Uri e, StringBuilder sb) | ||
175 | { | ||
176 | if(e == null) | ||
177 | { | ||
178 | sb.Append("<uri />"); | ||
179 | return; | ||
180 | } | ||
181 | |||
182 | string s; | ||
183 | if (e.IsAbsoluteUri) | ||
184 | s = e.AbsoluteUri; | ||
185 | else | ||
186 | s = e.ToString(); | ||
187 | |||
188 | if(String.IsNullOrEmpty(s)) | ||
189 | sb.Append("<uri />"); | ||
190 | else | ||
191 | { | ||
192 | sb.Append("<uri>"); | ||
193 | sb.Append(s); | ||
194 | sb.Append("</uri>"); | ||
195 | } | ||
196 | } | ||
197 | |||
198 | public static void AddElem(DateTime e, StringBuilder sb) | ||
199 | { | ||
200 | DateTime u = e.ToUniversalTime(); | ||
201 | if(u == depoch) | ||
202 | { | ||
203 | sb.Append("<date />"); | ||
204 | return; | ||
205 | } | ||
206 | string format; | ||
207 | if(u.Hour == 0 && u.Minute == 0 && u.Second == 0) | ||
208 | format = "yyyy-MM-dd"; | ||
209 | else if (u.Millisecond > 0) | ||
210 | format = "yyyy-MM-ddTHH:mm:ss.ffZ"; | ||
211 | else | ||
212 | format = "yyyy-MM-ddTHH:mm:ssZ"; | ||
213 | sb.Append("<date>"); | ||
214 | sb.Append(u.ToString(format,CultureInfo.InvariantCulture)); | ||
215 | sb.Append("</date>"); | ||
216 | } | ||
217 | |||
218 | //************ key value ******************* | ||
219 | // assumes name is a valid llsd key | ||
220 | |||
221 | public static void AddStartMap(string name, StringBuilder sb) | ||
222 | { | ||
223 | sb.Append("<key>"); | ||
224 | sb.Append(name); | ||
225 | sb.Append("</key><map>"); | ||
226 | } | ||
227 | |||
228 | public static void AddEmpyMap(string name, StringBuilder sb) | ||
229 | { | ||
230 | sb.Append("<key>"); | ||
231 | sb.Append(name); | ||
232 | sb.Append("</key><map />"); | ||
233 | } | ||
234 | |||
235 | // array == a list values | ||
236 | public static void AddStartArray(string name, StringBuilder sb) | ||
237 | { | ||
238 | sb.Append("<key>"); | ||
239 | sb.Append(name); | ||
240 | sb.Append("</key><array>"); | ||
241 | } | ||
242 | |||
243 | public static void AddEmpyArray(string name, StringBuilder sb) | ||
244 | { | ||
245 | sb.Append("<key>"); | ||
246 | sb.Append(name); | ||
247 | sb.Append("</key><array />"); | ||
248 | } | ||
249 | |||
250 | // undefined or null | ||
251 | public static void AddUnknownElem(string name, StringBuilder sb) | ||
252 | { | ||
253 | sb.Append("<key>"); | ||
254 | sb.Append(name); | ||
255 | sb.Append("</key><undef />"); | ||
256 | } | ||
257 | |||
258 | public static void AddElem(string name, bool e, StringBuilder sb) | ||
259 | { | ||
260 | sb.Append("<key>"); | ||
261 | sb.Append(name); | ||
262 | sb.Append("</key>"); | ||
263 | |||
264 | if(e) | ||
265 | sb.Append("<boolean>1</boolean>"); | ||
266 | else | ||
267 | sb.Append("<boolean />"); | ||
268 | } | ||
269 | |||
270 | public static void AddElem(string name, int e, StringBuilder sb) | ||
271 | { | ||
272 | sb.Append("<key>"); | ||
273 | sb.Append(name); | ||
274 | sb.Append("</key>"); | ||
275 | |||
276 | if(e == 0) | ||
277 | sb.Append("<integer />"); | ||
278 | else | ||
279 | { | ||
280 | sb.Append("<integer>"); | ||
281 | sb.Append(e.ToString()); | ||
282 | sb.Append("</integer>"); | ||
283 | } | ||
284 | } | ||
285 | |||
286 | public static void AddElem(string name, float e, StringBuilder sb) | ||
287 | { | ||
288 | sb.Append("<key>"); | ||
289 | sb.Append(name); | ||
290 | sb.Append("</key>"); | ||
291 | |||
292 | if(e == 0) | ||
293 | sb.Append("<real />"); | ||
294 | else | ||
295 | { | ||
296 | sb.Append("<real>"); | ||
297 | sb.Append(e.ToString(CultureInfo.InvariantCulture)); | ||
298 | sb.Append("</real>"); | ||
299 | } | ||
300 | } | ||
301 | |||
302 | public static void AddElem(string name, double e, StringBuilder sb) | ||
303 | { | ||
304 | sb.Append("<key>"); | ||
305 | sb.Append(name); | ||
306 | sb.Append("</key>"); | ||
307 | |||
308 | if(e == 0) | ||
309 | sb.Append("<real />"); | ||
310 | else | ||
311 | { | ||
312 | sb.Append("<real>"); | ||
313 | sb.Append(e.ToString(CultureInfo.InvariantCulture)); | ||
314 | sb.Append("</real>"); | ||
315 | } | ||
316 | } | ||
317 | |||
318 | public static void AddElem(string name, UUID e, StringBuilder sb) | ||
319 | { | ||
320 | sb.Append("<key>"); | ||
321 | sb.Append(name); | ||
322 | sb.Append("</key>"); | ||
323 | |||
324 | if(e == UUID.Zero) | ||
325 | sb.Append("<uuid />"); | ||
326 | else | ||
327 | { | ||
328 | sb.Append("<uuid>"); | ||
329 | EscapeToXML(e.ToString(), sb); | ||
330 | sb.Append("</uuid>"); | ||
331 | } | ||
332 | } | ||
333 | |||
334 | public static void AddElem(string name, string e, StringBuilder sb) | ||
335 | { | ||
336 | sb.Append("<key>"); | ||
337 | sb.Append(name); | ||
338 | sb.Append("</key>"); | ||
339 | |||
340 | if(String.IsNullOrEmpty(e)) | ||
341 | sb.Append("<string />"); | ||
342 | else | ||
343 | { | ||
344 | sb.Append("<string>"); | ||
345 | sb.Append(e.ToString()); | ||
346 | sb.Append("</string>"); | ||
347 | } | ||
348 | } | ||
349 | |||
350 | public static void AddRawElem(string name, string e, StringBuilder sb) | ||
351 | { | ||
352 | sb.Append("<key>"); | ||
353 | sb.Append(name); | ||
354 | sb.Append("</key>"); | ||
355 | |||
356 | if(String.IsNullOrEmpty(e)) | ||
357 | sb.Append("<string />"); | ||
358 | else | ||
359 | { | ||
360 | sb.Append("<string>"); | ||
361 | sb.Append(e); | ||
362 | sb.Append("</string>"); | ||
363 | } | ||
364 | } | ||
365 | |||
366 | public static void AddURIElem(string name, Uri e, StringBuilder sb) | ||
367 | { | ||
368 | sb.Append("<key>"); | ||
369 | sb.Append(name); | ||
370 | sb.Append("</key>"); | ||
371 | |||
372 | if(e == null) | ||
373 | { | ||
374 | sb.Append("<uri />"); | ||
375 | return; | ||
376 | } | ||
377 | |||
378 | string s; | ||
379 | if (e.IsAbsoluteUri) | ||
380 | s = e.AbsoluteUri; | ||
381 | else | ||
382 | s = e.ToString(); | ||
383 | |||
384 | if(String.IsNullOrEmpty(s)) | ||
385 | sb.Append("<uri />"); | ||
386 | else | ||
387 | { | ||
388 | sb.Append("<uri>"); | ||
389 | sb.Append(s); | ||
390 | sb.Append("</uri>"); | ||
391 | } | ||
392 | } | ||
393 | |||
394 | public static void AddElem(string name, DateTime e, StringBuilder sb) | ||
395 | { | ||
396 | sb.Append("<key>"); | ||
397 | sb.Append(name); | ||
398 | sb.Append("</key>"); | ||
399 | |||
400 | DateTime u = e.ToUniversalTime(); | ||
401 | if(u == depoch) | ||
402 | { | ||
403 | sb.Append("<date />"); | ||
404 | return; | ||
405 | } | ||
406 | string format; | ||
407 | if(u.Hour == 0 && u.Minute == 0 && u.Second == 0) | ||
408 | format = "yyyy-MM-dd"; | ||
409 | else if (u.Millisecond > 0) | ||
410 | format = "yyyy-MM-ddTHH:mm:ss.ffZ"; | ||
411 | else | ||
412 | format = "yyyy-MM-ddTHH:mm:ssZ"; | ||
413 | sb.Append("<date>"); | ||
414 | sb.Append(u.ToString(format,CultureInfo.InvariantCulture)); | ||
415 | sb.Append("</date>"); | ||
416 | } | ||
417 | |||
418 | public static void AddLLSD(string e, StringBuilder sb) | ||
419 | { | ||
420 | sb.Append(e); | ||
421 | } | ||
422 | |||
423 | public static void EscapeToXML(string s, StringBuilder sb) | ||
424 | { | ||
425 | int i; | ||
426 | char c; | ||
427 | String t; | ||
428 | int len = s.Length; | ||
429 | |||
430 | for (i = 0; i < len; i++) | ||
431 | { | ||
432 | c = s[i]; | ||
433 | switch (c) | ||
434 | { | ||
435 | case '<': | ||
436 | sb.Append("<"); | ||
437 | break; | ||
438 | case '>': | ||
439 | sb.Append(">"); | ||
440 | break; | ||
441 | case '&': | ||
442 | sb.Append("&"); | ||
443 | break; | ||
444 | case '"': | ||
445 | sb.Append("""); | ||
446 | break; | ||
447 | case '\\': | ||
448 | sb.Append("'"); | ||
449 | break; | ||
450 | default: | ||
451 | sb.Append(c); | ||
452 | break; | ||
453 | } | ||
454 | } | ||
455 | } | ||
456 | } | ||
457 | } | ||
diff --git a/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs b/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs index daa40c4..85e5ec3 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/BunchOfCaps/BunchOfCaps.cs | |||
@@ -1824,52 +1824,51 @@ namespace OpenSim.Region.ClientStack.Linden | |||
1824 | 1824 | ||
1825 | Dictionary<UUID,string> names = m_UserManager.GetUsersNames(ids); | 1825 | Dictionary<UUID,string> names = m_UserManager.GetUsersNames(ids); |
1826 | 1826 | ||
1827 | OSDMap osdReply = new OSDMap(); | 1827 | StringBuilder lsl = new StringBuilder(names.Count * 256 + 256); |
1828 | OSDArray agents = new OSDArray(); | 1828 | LLSDxmlEncode.AddStartHeader(lsl); |
1829 | 1829 | LLSDxmlEncode.AddStartMap(lsl); | |
1830 | osdReply["agents"] = agents; | 1830 | if(names.Count == 0) |
1831 | foreach (KeyValuePair<UUID,string> kvp in names) | 1831 | LLSDxmlEncode.AddEmpyArray("agents", lsl); |
1832 | else | ||
1832 | { | 1833 | { |
1833 | if (string.IsNullOrEmpty(kvp.Value)) | 1834 | LLSDxmlEncode.AddStartArray("agents", lsl); |
1834 | continue; | 1835 | |
1835 | if(kvp.Key == UUID.Zero) | 1836 | foreach (KeyValuePair<UUID,string> kvp in names) |
1836 | continue; | 1837 | { |
1838 | if (string.IsNullOrEmpty(kvp.Value)) | ||
1839 | continue; | ||
1840 | if(kvp.Key == UUID.Zero) | ||
1841 | continue; | ||
1837 | 1842 | ||
1838 | string[] parts = kvp.Value.Split(new char[] {' '}); | 1843 | string[] parts = kvp.Value.Split(new char[] {' '}); |
1839 | OSDMap osdname = new OSDMap(); | ||
1840 | 1844 | ||
1841 | // dont tell about unknown users, we can't send them back on Bad either | 1845 | // dont tell about unknown users, we can't send them back on Bad either |
1842 | if(parts[0] == "Unknown") | 1846 | if(parts[0] == "Unknown") |
1843 | continue; | 1847 | continue; |
1844 | /* | 1848 | |
1845 | if(parts[0] == "Unknown") | 1849 | LLSDxmlEncode.AddStartMap(lsl); |
1846 | { | 1850 | LLSDxmlEncode.AddElem("display_name_next_update", DateTime.UtcNow.AddDays(8), lsl); |
1847 | osdname["display_name_next_update"] = OSD.FromDate(DateTime.UtcNow.AddHours(1)); | 1851 | LLSDxmlEncode.AddElem("display_name_expires", DateTime.UtcNow.AddMonths(1), lsl); |
1848 | osdname["display_name_expires"] = OSD.FromDate(DateTime.UtcNow.AddHours(2)); | 1852 | LLSDxmlEncode.AddElem("display_name", kvp.Value, lsl); |
1849 | } | 1853 | LLSDxmlEncode.AddElem("legacy_first_name", parts[0], lsl); |
1850 | else | 1854 | LLSDxmlEncode.AddElem("legacy_last_name", parts[1], lsl); |
1851 | */ | 1855 | LLSDxmlEncode.AddElem("username", kvp.Value, lsl); |
1852 | { | 1856 | LLSDxmlEncode.AddElem("id", kvp.Key, lsl); |
1853 | osdname["display_name_next_update"] = OSD.FromDate(DateTime.UtcNow.AddDays(8)); | 1857 | LLSDxmlEncode.AddElem("is_display_name_default", true, lsl); |
1854 | osdname["display_name_expires"] = OSD.FromDate(DateTime.UtcNow.AddMonths(1)); | 1858 | LLSDxmlEncode.AddEndMap(lsl); |
1855 | } | 1859 | } |
1856 | osdname["display_name"] = OSD.FromString(kvp.Value); | 1860 | LLSDxmlEncode.AddEndArray(lsl); |
1857 | osdname["legacy_first_name"] = parts[0]; | ||
1858 | osdname["legacy_last_name"] = parts[1]; | ||
1859 | osdname["username"] = OSD.FromString(kvp.Value); | ||
1860 | osdname["id"] = OSD.FromUUID(kvp.Key); | ||
1861 | osdname["is_display_name_default"] = OSD.FromBoolean(true); | ||
1862 | |||
1863 | agents.Add(osdname); | ||
1864 | } | 1861 | } |
1862 | |||
1863 | LLSDxmlEncode.AddEndMap(lsl); | ||
1864 | LLSDxmlEncode.AddEndHeader(lsl); | ||
1865 | 1865 | ||
1866 | // Full content request | 1866 | // Full content request |
1867 | httpResponse.StatusCode = (int)System.Net.HttpStatusCode.OK; | 1867 | httpResponse.StatusCode = (int)System.Net.HttpStatusCode.OK; |
1868 | //httpResponse.ContentLength = ??; | 1868 | //httpResponse.ContentLength = ??; |
1869 | httpResponse.ContentType = "application/llsd+xml"; | 1869 | httpResponse.ContentType = "application/llsd+xml"; |
1870 | 1870 | ||
1871 | string reply = OSDParser.SerializeLLSDXmlString(osdReply); | 1871 | return lsl.ToString(); |
1872 | return reply; | ||
1873 | } | 1872 | } |
1874 | } | 1873 | } |
1875 | 1874 | ||