source: trunk/CrypPlugins/WorkspaceManager/Model/XMLSerialization.cs @ 2280

Last change on this file since 2280 was 2128, checked in by kopal, 11 years ago

fixed erroneous serialization of Windows.System.Point in the XMLSerialization

File size: 32.2 KB
Line 
1/*                             
2   Copyright 2010 Nils Kopal, Viktor M.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17using System;
18using System.Collections.Generic;
19using System.Linq;
20using System.Text;
21using System.Reflection;
22using System.IO;
23using System.Xml;
24using System.Collections;
25using System.IO.Compression;
26using Cryptool.PluginBase;
27using WorkspaceManager;
28using System.Windows;
29
30namespace XMLSerialization
31{
32    /// <summary>
33    /// Provides static methods for XML serialization and deserialization
34    /// </summary>
35    public class XMLSerialization
36    {
37        /// <summary>
38        /// Serializes the given object and all of its members to the given file using UTF-8 encoding
39        /// Works only on objects which are marked as "Serializable"
40        /// If compress==true then GZip is used for compressing
41        /// </summary>
42        /// <param name="obj"></param>
43        /// <param name="filename"></param>
44        /// /// <param name="compress"></param>
45        public static void Serialize(object obj, string filename,bool compress = false)
46        {
47            XMLSerialization.Serialize(obj, filename, Encoding.UTF8,compress);
48        }
49
50        /// <summary>
51        /// Serializes the given object and all of its members to the given file using
52        /// the given encoding
53        /// Works only on objects which are marked as "Serializable"
54        /// If compress==true then GZip is used for compressing
55        /// </summary>
56        /// <param name="obj"></param>
57        /// <param name="filename"></param>
58        /// <param name="compress"></param>
59        public static void Serialize(object obj, string filename,Encoding encoding,bool compress = false)
60        {
61
62            FileStream sourceFile = File.Create(filename);
63            if (compress)
64            {
65                GZipStream compStream = new GZipStream(sourceFile, CompressionMode.Compress);
66                StreamWriter writer = new StreamWriter(compStream);
67                try
68                {
69
70                    XMLSerialization.Serialize(obj, writer,compress);
71                }
72                finally
73                {
74                    if (writer != null)
75                    {
76                        writer.Close();
77                    }
78                    if (compStream != null)
79                    {
80                        compStream.Dispose();
81                    }
82                    if (sourceFile != null)
83                    {
84                        sourceFile.Close();
85                    }
86                }
87            }
88            else
89            {
90                StreamWriter writer = new StreamWriter(sourceFile);
91                try
92                {
93                   
94                    XMLSerialization.Serialize(obj, writer);
95                }
96                finally
97                {
98                    if (writer != null)
99                    {
100                        writer.Close();
101                    }                   
102                    if (sourceFile != null)
103                    {
104                        sourceFile.Close();
105                    }
106                }
107            }
108        }
109        /// <summary>
110        /// Serializes the given object and all of its members to the given writer as xml
111        /// Works only on objects which are marked as "Serializable"
112        /// </summary>
113        /// <param name="obj"></param>
114        /// <param name="writer"></param>
115        public static void Serialize(object obj, StreamWriter writer,bool compress=false)
116        {
117            HashSet<object> alreadySerializedObjects = new HashSet<object>();
118
119            writer.WriteLine("<?xml version=\"1.0\" encoding=\"" + writer.Encoding.HeaderName + "\"?>");
120            writer.WriteLine("<!--");
121            writer.WriteLine("     XML serialized C# Objects");
122            writer.WriteLine("     File created: " + System.DateTime.Now);
123            writer.WriteLine("     File compressed: " + compress);
124            writer.WriteLine("     XMLSerialization created by Nils Kopal");
125            writer.WriteLine("     mailto: Nils.Kopal(AT)stud.uni-due.de");
126            writer.WriteLine("-->");
127            writer.WriteLine("<objects>");
128            SerializeIt(obj, writer, alreadySerializedObjects);
129            writer.WriteLine("</objects>");
130            writer.Flush();
131        }
132
133        /// <summary>
134        /// Serializes the given object and all of its members to the given writer as xml
135        /// Works only on object which are marked as "Serializable"
136        /// </summary>
137        /// <param name="obj"></param>
138        /// <param name="writer"></param>
139        private static void SerializeIt(object obj, StreamWriter writer,HashSet<object> alreadySerializedObjects)
140        {
141            //we only work on complex objects which are serializable and we did not see before
142            if (obj == null || 
143                isPrimitive(obj) || 
144                !obj.GetType().IsSerializable || 
145                alreadySerializedObjects.Contains(obj))
146            {
147                return;
148            }
149
150            MemberInfo[] memberInfos = obj.GetType().FindMembers(
151                MemberTypes.All, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, new MemberFilter(DelegateToSearchCriteria), "ReferenceEquals");
152           
153            writer.WriteLine("<object>");
154            writer.WriteLine("<type>" + obj.GetType().FullName + "</type>");
155            writer.WriteLine("<id>" + obj.GetHashCode() + "</id>");
156         
157            writer.WriteLine("<members>");
158
159            foreach (MemberInfo memberInfo in memberInfos)
160            {
161                if (memberInfo.MemberType == MemberTypes.Field && !obj.GetType().GetField(memberInfo.Name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).IsNotSerialized)
162                {
163                    string type = obj.GetType().GetField(memberInfo.Name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).FieldType.FullName;
164                    object value = obj.GetType().GetField(memberInfo.Name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).GetValue(obj);
165
166                    if (value is System.Collections.IList && value.GetType().IsGenericType)
167                    {
168                        string gentype = value.GetType().GetGenericArguments()[0].FullName;
169                        type = "System.Collections.Generic.List;" + gentype;
170                    }
171
172                    writer.WriteLine("<member>");
173                    writer.WriteLine("<name>" + ReplaceXMLSymbols(memberInfo.Name) + "</name>");
174                    writer.WriteLine("<type>" + ReplaceXMLSymbols(type) + "</type>");
175
176                    if (value is System.Byte[])
177                    {
178                        byte[] bytes = (byte[])value;
179                        writer.WriteLine("<value><![CDATA[" + ReplaceXMLSymbols(Convert.ToBase64String(bytes)) + "]]></value>");
180                    }                   
181                    else if (value is System.Collections.IList)
182                    {
183                        writer.WriteLine("<list>");
184                        foreach (object o in (System.Collections.IList)value)
185                        {
186                            if (o.GetType().IsSerializable)
187                            {
188                                writer.WriteLine("<entry>");
189                                writer.WriteLine("<type>" + o.GetType().FullName + "</type>");
190                                if (isPrimitive(o))
191                                {
192                                    if (o is Enum)
193                                    {
194                                        writer.WriteLine("<value>" + o.GetHashCode() + "</value>");
195                                    }
196                                    else if(o is Point)
197                                    {
198                                        Point p = (Point) o;
199                                        writer.WriteLine("<value><![CDATA[" + p.X + ";" + p.Y + "]]></value>");
200
201                                    }
202                                    else
203                                    {
204                                        writer.WriteLine("<value><![CDATA[" + o + "]]></value>");
205                                    }
206                                }
207                                else
208                                {
209                                    writer.WriteLine("<reference>" + o.GetHashCode() + "</reference>");
210                                }
211                                writer.WriteLine("</entry>");
212                            }
213                        }
214                        writer.WriteLine("</list>");
215                    }
216                    else if (value == null)
217                    {
218                        writer.WriteLine("<value></value>");
219                    }
220                    else if (isPrimitive(value))
221                    {
222                        if (value is Enum)
223                        {
224                            writer.WriteLine("<value>" + value.GetHashCode() + "</value>");
225                        }
226                        else if(value is Point)
227                        {
228                            Point p = (Point)value;
229                            writer.WriteLine("<value><![CDATA[" + p.X + ";" + p.Y + "]]></value>");   
230                        }
231                        else
232                        {
233                            writer.WriteLine("<value><![CDATA[" + value.ToString() + "]]></value>");
234                        }
235                    }
236                    else
237                    {
238                        writer.WriteLine("<reference>" + value.GetHashCode() + "</reference>");
239                    }
240                    writer.WriteLine("</member>");
241                }
242            }
243            writer.WriteLine("</members>");           
244            writer.WriteLine("</object>");
245            writer.Flush();
246           
247            //Save obj so that we will not work on it again
248            alreadySerializedObjects.Add(obj);
249
250            foreach (MemberInfo memberInfo in memberInfos)
251            {
252                if (memberInfo.MemberType == MemberTypes.Field)
253                {
254                    string type = obj.GetType().GetField(memberInfo.Name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).FieldType.FullName;
255                    object value = obj.GetType().GetField(memberInfo.Name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).GetValue(obj);
256                   
257                    if (value is System.Collections.IList && !(value is System.Byte[]))
258                    {
259                        foreach (object o in (System.Collections.IList)value)
260                        {
261                            SerializeIt(o, writer, alreadySerializedObjects);
262                        }
263                    }
264                    else
265                    {
266                        SerializeIt(value, writer, alreadySerializedObjects);
267                    }
268                   
269                }             
270            }
271        }
272
273        /// <summary>
274        /// Check if the given object ist Primitve
275        /// Primitive means isPrimitive returns true
276        /// or Fullname does not start with "System"
277        /// </summary>
278        /// <param name="o"></param>
279        /// <returns></returns>
280        private static Boolean isPrimitive(object o)       
281        {
282            if (o == null)
283            {
284                return false;
285            }
286            if (o is Enum)
287            {
288                return true;
289            }
290
291            return (o.GetType().IsPrimitive || o.GetType().FullName.Substring(0, 6).Equals("System"));
292        }
293
294        /// <summary>
295        /// Returns true if MemberType is Field or Property
296        /// </summary>
297        /// <param name="objMemberInfo"></param>
298        /// <param name="objSearch"></param>
299        /// <returns></returns>
300        private static bool DelegateToSearchCriteria(MemberInfo objMemberInfo, Object objSearch)
301        {
302            if (objMemberInfo.MemberType == MemberTypes.Field)
303            {
304                return true;
305            }
306            else
307            {
308                return false;
309            }
310        }
311
312        /// <summary>
313        /// Replaces
314        /// <           with            &lt;
315        /// >           with            &gt;
316        /// &           with            &amp;
317        /// "           with            &quot;
318        /// '           with            &apos;
319        /// If input string is null it returns "null" string
320        /// </summary>
321        /// <param name="str"></param>
322        /// <returns></returns>
323        private static string ReplaceXMLSymbols(String str)
324        {
325            if (str == null)
326            {
327                return "null";
328            }
329
330            return str.
331                Replace("<", "&lt;").
332                Replace(">", "&gt").
333                Replace("&", "&amp;").
334                Replace("\"", "&quot;").
335                Replace("'", "&apos;");
336        }
337
338        /// <summary>
339        /// Inverse to ReplaceXMLSymbols
340        /// </summary>
341        /// <param name="str"></param>
342        /// <returns></returns>
343        private static string RevertXMLSymbols(String str)
344        {
345            if (str == null)
346            {
347                return "null";
348            }
349
350            return str.
351                Replace("&lt;","<").
352                Replace("&gt", ">").
353                Replace("&amp;","&").
354                Replace("&quot;","\"").
355                Replace("&apos;","'");
356        }
357
358        /// <summary>
359        /// Deserializes the given XML and returns the root as obj
360        /// </summary>
361        /// <param name="filename"></param>
362        /// <param name="compress"></param>
363        /// <param name="workspaceManager"></param>
364        /// <returns></returns>
365        public static object Deserialize(String filename, bool compress=false,WorkspaceManager.WorkspaceManager workspaceManager = null)
366        {
367            FileStream sourceFile = File.OpenRead(filename);
368            XmlDocument doc = new XmlDocument(); ;
369            GZipStream compStream = null;
370
371            if (compress)
372            {
373                compStream = new GZipStream(sourceFile, CompressionMode.Decompress);
374                doc.Load(compStream);
375            }
376            else
377            {
378                doc.Load(sourceFile);
379            }
380
381            try
382            {
383                return XMLSerialization.Deserialize(doc,workspaceManager);
384            }
385            finally
386            {
387                if (compStream != null)
388                {
389                    compStream.Close();
390                }
391            }
392        }
393
394        /// <summary>
395        /// Deserializes the given XMLDocument and returns the root as obj
396        /// </summary>
397        /// <param name="doc"></param>
398        /// <param name="workspaceManager"></param>
399        /// <returns></returns>
400        public static object Deserialize(XmlDocument doc, WorkspaceManager.WorkspaceManager workspaceManager = null)
401        {
402            Dictionary<string, object> createdObjects = new Dictionary<string, object>();
403            LinkedList<object[]> links = new LinkedList<object[]>();
404
405            XmlElement objects = doc.DocumentElement;
406
407            foreach (XmlNode objct in objects.ChildNodes)
408            {
409                XmlNode type = objct.ChildNodes[0];
410                XmlNode id = objct.ChildNodes[1];
411                XmlNode members = objct.ChildNodes[2];
412
413                object newObject = System.Activator.CreateInstance(Type.GetType(type.InnerText));
414                createdObjects.Add(id.InnerText, newObject);
415
416                foreach (XmlNode member in members.ChildNodes)
417                {
418                    XmlNode membername = member.ChildNodes[0];
419                    XmlNode membertype = member.ChildNodes[1];
420
421                    object newmember;
422
423                    try
424                    {
425                        if (member.ChildNodes[2].Name.Equals("value"))
426                        {
427
428                            XmlNode value = member.ChildNodes[2];
429                            if (RevertXMLSymbols(membertype.InnerText).Equals("System.String"))
430                            {
431
432                                newObject.GetType().GetField(RevertXMLSymbols(membername.InnerText),
433                                                             BindingFlags.NonPublic |
434                                                             BindingFlags.Public |
435                                                             BindingFlags.Instance).SetValue(newObject, value.InnerText);
436                            }
437                            else if (RevertXMLSymbols(membertype.InnerText).Contains("System.Int"))
438                            {
439                                Int32 result = 0;
440                                System.Int32.TryParse(RevertXMLSymbols(value.InnerText), out result);
441                                newObject.GetType().GetField(RevertXMLSymbols(membername.InnerText),
442                                                             BindingFlags.NonPublic |
443                                                             BindingFlags.Public |
444                                                             BindingFlags.Instance).SetValue(newObject, result);
445                            }
446                            else if (RevertXMLSymbols(membertype.InnerText).Equals("System.Double"))
447                            {
448                                Double result = 0;
449                                System.Double.TryParse(RevertXMLSymbols(value.InnerText), out result);
450                                newObject.GetType().GetField(RevertXMLSymbols(membername.InnerText),
451                                                             BindingFlags.NonPublic |
452                                                             BindingFlags.Public |
453                                                             BindingFlags.Instance).SetValue(newObject, result);
454                            }
455                            else if (RevertXMLSymbols(membertype.InnerText).Equals("System.Char"))
456                            {
457                                Char result = ' ';
458                                System.Char.TryParse(RevertXMLSymbols(value.InnerText), out result);
459                                newObject.GetType().GetField(RevertXMLSymbols(membername.InnerText),
460                                                             BindingFlags.NonPublic |
461                                                             BindingFlags.Public |
462                                                             BindingFlags.Instance).SetValue(newObject, result);
463                            }
464                            else if (RevertXMLSymbols(membertype.InnerText).Equals("System.Boolean"))
465                            {
466                                Boolean result = false;
467                                System.Boolean.TryParse(RevertXMLSymbols(value.InnerText), out result);
468                                newObject.GetType().GetField(RevertXMLSymbols(membername.InnerText),
469                                                             BindingFlags.NonPublic |
470                                                             BindingFlags.Public |
471                                                             BindingFlags.Instance).SetValue(newObject, result);
472                            }
473                            else if (RevertXMLSymbols(membertype.InnerText).Equals("System.Windows.Point"))
474                            {
475                                string[] values = value.InnerText.Split(new char[] {';'});
476
477                                if(values.Length != 2)
478                                {
479                                    throw new Exception("Can not create a Point with " + values.Length + " Coordinates!");
480                                }
481
482                                double x = 0;
483                                double y = 0;
484                                double.TryParse(values[0], out x);
485                                double.TryParse(values[1], out y);
486
487                                System.Windows.Point result = new System.Windows.Point(x, y);
488                                newObject.GetType().GetField(RevertXMLSymbols(membername.InnerText),
489                                                             BindingFlags.NonPublic |
490                                                             BindingFlags.Public |
491                                                             BindingFlags.Instance).SetValue(newObject, result);
492                            }
493                            else if (RevertXMLSymbols(membertype.InnerText).Equals("System.Byte[]"))
494                            {
495                                byte[] bytearray = Convert.FromBase64String(value.InnerText);
496
497                                newObject.GetType().GetField(RevertXMLSymbols(membername.InnerText),
498                                                             BindingFlags.NonPublic |
499                                                             BindingFlags.Public |
500                                                             BindingFlags.Instance).SetValue(newObject, bytearray);
501                            }
502                            else
503                            {
504                                newmember =
505                                    System.Activator.CreateInstance(Type.GetType(RevertXMLSymbols(membertype.InnerText)));
506
507                                if (newmember is Enum)
508                                {
509                                    Int32 result = 0;
510                                    System.Int32.TryParse(RevertXMLSymbols(value.InnerText), out result);
511                                    object newEnumValue =
512                                        Enum.ToObject(Type.GetType(RevertXMLSymbols(membertype.InnerText)), result);
513
514                                    newObject.GetType().GetField(RevertXMLSymbols(membername.InnerText),
515                                                                 BindingFlags.NonPublic |
516                                                                 BindingFlags.Public |
517                                                                 BindingFlags.Instance).SetValue(newObject, newEnumValue);
518                                }
519                                else
520                                {
521                                    newObject.GetType().GetField(RevertXMLSymbols(membername.InnerText),
522                                                                 BindingFlags.NonPublic |
523                                                                 BindingFlags.Public |
524                                                                 BindingFlags.Instance).SetValue(newObject, newmember);
525                                }
526
527                            }
528                        }
529                        else if (member.ChildNodes[2].Name.Equals("reference"))
530                        {
531                            XmlNode reference = member.ChildNodes[2];
532                            links.AddLast(new object[]
533                                              {
534                                                  newObject,
535                                                  RevertXMLSymbols(membername.InnerText),
536                                                  RevertXMLSymbols(reference.InnerText),
537                                                  false
538                                              });
539                        }
540                        else if (member.ChildNodes[2].Name.Equals("list"))
541                        {
542                            String[] types = RevertXMLSymbols(membertype.InnerText).Split(';');
543
544                            if (types.Length == 1)
545                            {
546                                newmember = System.Activator.CreateInstance(Type.GetType(types[0]));
547                                newObject.GetType().GetField(RevertXMLSymbols(membername.InnerText),
548                                                             BindingFlags.NonPublic |
549                                                             BindingFlags.Public |
550                                                             BindingFlags.Instance).SetValue(newObject, newmember);
551                            }
552                            else if (types.Length == 2)
553                            {
554                                //we have 2 types, that means that we have a generic list with generic type types[1]
555                                Type t = typeof (System.Collections.Generic.List<>);
556                                Type[] typeArgs = {Type.GetType(types[1])};
557                                Type constructed = t.MakeGenericType(typeArgs);
558                                newmember = Activator.CreateInstance(constructed);
559                                newObject.GetType().GetField(RevertXMLSymbols(membername.InnerText),
560                                                             BindingFlags.NonPublic |
561                                                             BindingFlags.Public |
562                                                             BindingFlags.Instance).SetValue(newObject, newmember);
563                            }
564                            else
565                            {
566                                throw new Exception("Expected 1 or 2 types for list; But found:" + types.Length);
567                            }
568
569                            foreach (XmlNode entry in member.ChildNodes[2].ChildNodes)
570                            {
571                                if (entry.ChildNodes[1].Name.Equals("reference"))
572                                {
573                                    XmlNode reference = entry.ChildNodes[1];
574                                    links.AddLast(new object[]
575                                                      {
576                                                          newObject,
577                                                          RevertXMLSymbols(membername.InnerText),
578                                                          RevertXMLSymbols(reference.InnerText),
579                                                          true
580                                                      });
581                                }
582                                else
583                                {
584                                    XmlNode typ = entry.ChildNodes[1];
585                                    XmlNode value = entry.ChildNodes[1];
586                                    if (RevertXMLSymbols(typ.InnerText).Equals("System.String"))
587                                    {
588
589                                        ((IList) newmember).Add(RevertXMLSymbols(value.InnerText));
590                                    }
591                                    else if (RevertXMLSymbols(typ.InnerText).Equals("System.Int16"))
592                                    {
593                                        Int16 result = 0;
594                                        System.Int16.TryParse(RevertXMLSymbols(value.InnerText), out result);
595                                        ((IList) newmember).Add(result);
596                                    }
597                                    else if (RevertXMLSymbols(typ.InnerText).Equals("System.Int32"))
598                                    {
599                                        Int32 result = 0;
600                                        System.Int32.TryParse(RevertXMLSymbols(value.InnerText), out result);
601                                        ((IList) newmember).Add(result);
602                                    }
603                                    else if (RevertXMLSymbols(typ.InnerText).Equals("System.Int64"))
604                                    {
605                                        Int64 result = 0;
606                                        System.Int64.TryParse(RevertXMLSymbols(value.InnerText), out result);
607                                        ((IList) newmember).Add(result);
608                                    }
609                                    else if (RevertXMLSymbols(typ.InnerText).Equals("System.Double"))
610                                    {
611                                        Double result = 0;
612                                        System.Double.TryParse(RevertXMLSymbols(value.InnerText), out result);
613                                        ((IList) newmember).Add(result);
614                                    }
615                                    else if (RevertXMLSymbols(typ.InnerText).Equals("System.Char"))
616                                    {
617                                        Char result = ' ';
618                                        System.Char.TryParse(RevertXMLSymbols(value.InnerText), out result);
619                                        ((IList) newmember).Add(result);
620                                    }
621                                }
622                            }
623                        }
624                    }
625                    catch(Exception ex)
626                    {
627                        if(workspaceManager != null)
628                        {
629                            workspaceManager.GuiLogMessage("Cold not deserialize model element \"" + membername.InnerText + "\" of type \"" + membertype.InnerText + "\" because of:" + ex.Message,NotificationLevel.Warning);
630                        }else
631                        {
632                            Console.WriteLine("Cold not deserialize model element \"" + membername.InnerText + "\" of type \"" + membertype.InnerText + "\" because of:" + ex.Message);
633                        }
634                    }
635                }
636            }
637
638            foreach (object[] triple in links)
639            {
640
641                object obj = triple[0];
642                string membername = (string)triple[1];
643                string reference = (string)triple[2];
644                bool isList = (bool)triple[3];
645                object obj2 = null;
646                createdObjects.TryGetValue(reference, out obj2);
647
648                try
649                {
650                    if (isList)
651                    {
652                        ((IList) obj.GetType().GetField(membername).GetValue(obj)).Add(obj2);
653                    }
654                    else
655                    {
656                        if (obj != null && obj2 != null)
657                        {
658                            FieldInfo fieldInfo = obj.GetType().GetField(membername,
659                                                                         BindingFlags.NonPublic |
660                                                                         BindingFlags.Public |
661                                                                         BindingFlags.Instance);
662
663                            fieldInfo.SetValue(obj, obj2);
664                        }
665                    }
666                }
667                catch(Exception ex)
668                {
669                    if (workspaceManager != null)
670                    {
671                        workspaceManager.GuiLogMessage("Cold not restore reference beteen model element \"" + membername + "\" and its reference with id \"" + reference + "\" because of:" + ex.Message, NotificationLevel.Warning);
672                    }
673                    else
674                    {
675                        Console.WriteLine("Cold not restore reference beteen model element \"" + membername + "\" and its reference with id \"" + reference + "\" because of:" + ex.Message);
676                    }
677                }
678            }
679
680            return createdObjects.Values.First();
681        }
682    }
683}
Note: See TracBrowser for help on using the repository browser.