abbr works only on full words; changes on nodes don't work
[osm-helpers.git] / osm-check.cs
blob00b8768908b9fa3177d79df9fc5587073a26654d
1 /*
2 * Author: Paolo Molaro
3 * Copyright (c) 2008 Paolo Molaro lupus@oddwiz.org
4 * License: MIT/X11, see the MIT.X11 file.
5 */
6 using System;
7 using System.IO;
8 using System.Net;
9 using System.Xml;
10 using System.Text;
11 using System.Collections.Generic;
12 using OpenStreetMap;
14 class OsmCheck {
16 static string[] way_types = new string[] {};
19 static string[] allowed_lowercase = new string[] {
20 "da", "di", "de", "del", "dei", "delle", "degli", "dell", "della", "dello",
21 "sul", "sull", "d", "al", "a", "in", "e"
25 static string[] abbreviations = new string[] {};
27 static Dictionary<string,string> wrong_tags_in_nodes = new Dictionary<string,string> ();
28 static Dictionary<string,string> wrong_tags_in_ways = new Dictionary<string,string> ();
30 static void LoadRules (string filename)
32 if (filename == null)
33 filename = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData), "osm-helpers/osm-check.rules");
34 XmlTextReader reader = new XmlTextReader (filename);
35 List<string> abbrs = new List<string> ();
36 List<string> ways = new List<string> ();
38 while (reader.Read ()) {
39 if (reader.NodeType == XmlNodeType.Element) {
40 string name = reader.Name;
42 if (name == "osmrules") {
43 continue;
45 if (name == "abbr") {
46 string from = null;
47 string to = null;
48 for (int i = 0; i < reader.AttributeCount; ++i) {
49 reader.MoveToAttribute (i);
50 if (reader.Name == "from")
51 from = reader.Value;
52 else if (reader.Name == "to")
53 to = reader.Value;
54 else
55 throw new Exception ("invalid attribute in abbr element");
57 if (from == null || to == null)
58 throw new Exception ("missing attributes in abbr element");
59 abbrs.Add (from);
60 abbrs.Add (to);
61 } else if (name == "way") {
62 string n = null;
63 for (int i = 0; i < reader.AttributeCount; ++i) {
64 reader.MoveToAttribute (i);
65 if (reader.Name == "name")
66 n = reader.Value;
67 else
68 throw new Exception ("invalid attribute in way element");
70 if (n == null)
71 throw new Exception ("missing name attribute in way element");
72 ways.Add (n);
73 } else if (name == "remove") {
74 string type = null;
75 string tag = null;
76 string val = "";
77 for (int i = 0; i < reader.AttributeCount; ++i) {
78 reader.MoveToAttribute (i);
79 if (reader.Name == "type")
80 type = reader.Value;
81 else if (reader.Name == "tag")
82 tag = reader.Value;
83 else if (reader.Name == "val")
84 val = reader.Value;
85 else
86 throw new Exception ("invalid attribute in remove element");
88 if (type == null || tag == null)
89 throw new Exception ("missing type or tag attribute in remove element");
90 if (type == "way")
91 wrong_tags_in_ways [tag] = val;
92 else if (type == "node")
93 wrong_tags_in_nodes [tag] = val;
94 else
95 throw new Exception ("invalid type attribute in remove element");
96 } else {
97 throw new Exception ("invalid element");
101 abbreviations = abbrs.ToArray ();
102 way_types = ways.ToArray ();
105 static XmlTextWriter writer;
107 static void EmitElement (OsmObject obj, string tag, string old, string newv)
109 writer.WriteStartElement (obj.ObjectType);
110 writer.WriteAttributeString ("id", obj.ID.ToString ());
111 writer.WriteAttributeString ("tag", tag);
112 writer.WriteAttributeString ("old", old);
113 writer.WriteAttributeString ("new", newv);
114 writer.WriteAttributeString ("user", obj.User);
115 writer.WriteEndElement ();
118 static string TrimIfNeeded (string tag, string val)
120 // we avoid changing note, as this makes it easier to track down
121 // who actually wrote the note and ask them about it
122 if (tag == "note")
123 return val;
124 if (char.IsWhiteSpace (val [0]) || char.IsWhiteSpace (val [val.Length - 1]))
125 val = val.Trim ();
126 for (int i = 0; i < val.Length; ++i) {
127 if (char.IsWhiteSpace (val [i]) && char.IsWhiteSpace (val [i + 1])) {
128 // good enough for the use case
129 val = val.Remove (i + 1, 1);
130 i--;
133 return val;
136 static string CheckStartWord (string tag, string val)
138 if (tag == "note")
139 return val;
140 foreach (string s in way_types) {
141 if (!val.StartsWith (s) && val.StartsWith (s, StringComparison.OrdinalIgnoreCase)) {
142 if (val.Length > s.Length && !char.IsWhiteSpace (val [s.Length]))
143 continue;
144 string newv = s + val.Substring (s.Length);
145 return newv;
148 return val;
151 static string CheckAbbreviations (string val)
153 for (int i = 0; i < abbreviations.Length; i += 2) {
154 string s = abbreviations [i];
155 // consider the abbreviation in any case variant
156 if (val.StartsWith (s, StringComparison.OrdinalIgnoreCase)) {
157 if (val.Length > s.Length && !char.IsWhiteSpace (val [s.Length]))
158 continue;
159 string newv = abbreviations [i + 1] + val.Substring (s.Length);
160 return newv;
163 return val;
166 static string CheckAllowedTags (OsmObject obj, string tag, string val)
168 if (obj is Node) {
169 if (wrong_tags_in_nodes.ContainsKey (tag)) {
170 string n = wrong_tags_in_nodes [tag];
171 if (n == "" || n == val)
172 return "";
173 return val;
175 } else if (obj is Way) {
176 if (wrong_tags_in_ways.ContainsKey (tag)) {
177 string n = wrong_tags_in_ways [tag];
178 if (n == "" || n == val)
179 return "";
180 return val;
183 return val;
187 static string CheckAllowedLowercase (OsmObject obj, string tag, string val)
189 // just for names, but we may want to consider only highways as well
190 if (tag != "name" || !(obj is Way))
191 return val;
192 if (obj ["highway"] == null)
193 return val;
194 for (int i = 0; i < val.Length; ++i) {
195 if (char.IsLower (val [i])) {
196 int j = i + 1;
197 while (j < val.Length && (!char.IsWhiteSpace (val [j]) && val [j] != '\''))
198 j++;
199 int count = j - i;
200 string word = val.Substring (i, count);
201 bool found = false;
202 foreach (string allowed in allowed_lowercase) {
203 if (string.CompareOrdinal (word, allowed) == 0) {
204 found = true;
205 break;
208 if (!found) {
209 StringBuilder sb = new StringBuilder (val);
210 sb [i] = char.ToUpper (val [i]);
211 Console.WriteLine ("{0} changed to {1}", val, sb.ToString ());
213 i = j - 1;
214 continue;
216 while (i < val.Length && (!char.IsWhiteSpace (val [i]) && val [i] != '\''))
217 i++;
218 while (i < val.Length && (char.IsWhiteSpace (val [i]) || val [i] == '\''))
219 i++;
220 i--;
222 return val;
226 static void Usage ()
228 Usage_r (1);
231 static void Usage_r (int retval)
233 Console.WriteLine ("Usage: osm-check [OPTIONS] file.osm");
234 Console.WriteLine (" osm-check [OPTIONS] lat lon [delta]");
235 Console.WriteLine (" osm-check --help");
236 Console.WriteLine ("Options:");
237 Console.WriteLine ("\t--help Print the help and exit");
238 Console.WriteLine ("\t--rules rules_file Read the checking rules from rules_file");
239 Console.WriteLine ("\t--out out_file Output the changes to out_file (default: stdout)");
240 Environment.Exit (retval);
243 static int Main (string[] args)
245 string rules = null;
246 string file = null;
247 string outfile = null;
248 bool coords = false;
249 double delta = 0.02;
250 double lat = 0;
251 double lon = 0;
253 for (int i = 0; i < args.Length; ++i) {
254 if (args [i] == "--help") {
255 Usage_r (0);
256 } else if (args [i] == "--rules") {
257 if (++i >= args.Length)
258 Usage ();
259 rules = args [i];
260 } else if (args [i] == "--out") {
261 if (++i >= args.Length)
262 Usage ();
263 outfile = args [i];
264 } else {
265 switch (args.Length - i) {
266 case 1:
267 file = args [i];
268 break;
269 case 3:
270 delta = double.Parse (args [i + 2]);
271 goto case 2;
272 case 2:
273 lat = double.Parse (args [i + 0]);
274 lon = double.Parse (args [i + 1]);
275 coords = true;
276 break;
277 default:
278 Usage ();
279 break;
283 if (file == null && !coords)
284 Usage ();
285 try {
286 LoadRules (rules);
287 } catch (Exception e) {
288 Console.WriteLine ("Cannot load rules: {0}", e.Message);
289 return 2;
292 DataBase db = new DataBase ();
293 IEnumerable<OsmObject> list;
294 if (file != null) {
295 list = DataBase.LoadObjects (file);
296 } else {
297 list = db.Load (lon-delta, lat-delta, lon+delta, lat+delta);
299 Stream outstream;
300 if (outfile == null)
301 outstream = Console.OpenStandardOutput ();
302 else
303 outstream = File.Create (outfile);
304 writer = new XmlTextWriter (outstream, new UTF8Encoding (false, false));
305 writer.WriteStartDocument ();
306 writer.WriteStartElement ("osmchanges");
307 writer.Formatting = Formatting.Indented;
308 foreach (OsmObject obj in list) {
309 foreach (string tag in obj.Tags) {
310 string val = obj [tag];
311 if (val == null || val.Length == 0)
312 continue;
313 string newv = TrimIfNeeded (tag, val);
314 if ((object)val != (object)newv) {
315 EmitElement (obj, tag, val, newv);
316 continue;
318 newv = CheckStartWord (tag, val);
319 if ((object)val != (object)newv) {
320 EmitElement (obj, tag, val, newv);
321 continue;
323 newv = CheckAbbreviations (val);
324 if ((object)val != (object)newv) {
325 EmitElement (obj, tag, val, newv);
326 continue;
328 newv = CheckAllowedTags (obj, tag, val);
329 if ((object)val != (object)newv) {
330 EmitElement (obj, tag, val, newv);
331 continue;
334 * too much noise for now
335 newv = CheckAllowedLowercase (obj, tag, val);
336 if ((object)val != (object)newv) {
337 EmitElement (obj, tag, val, newv);
338 continue;
342 writer.WriteEndElement ();
343 writer.WriteEndDocument ();
344 writer.Close ();
345 if (outfile != null)
346 outstream.Close ();
347 return 0;