Skip to content

Commit 689c752

Browse files
atsushienojonpryor
authored andcommitted
[generator] Replace XmlDocument with XLinq and report error location (#151)
XmlDocument never supported source locations, therefore it was impossible to report exact error location in metadata fixup (which was quite annoying). What were alternatives? - XPathDocument: it is no-go because it is not editable. ApiFixup step requires modifications on the API description XML tree. - XPathNavigator on XLinq: it seemed doable, because XLinq is for modifying XML tree, and it can create XPathNavigator from XNode. What actually happens, however, was that their XPathNavigator does not support CreateAttributes(), which was required for <attr /> operation. Another attempt to work around by set_OuterXml() failed because it also does not support ReplaceRange() which was internally used by OuterXml. Therefore, I ended up to entirely replace XmlDocument with XLinq. It was not limited to ApiFixup but also *everywhere* in generator, because the API descriptions are also the XPath query targets.
1 parent 4aa2cde commit 689c752

15 files changed

+243
-226
lines changed

src/utils/EnumMappings.Xml.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
using System.IO;
33
using System.Linq;
44
using System.Xml;
5-
5+
using System.Xml.Linq;
6+
using System.Xml.XPath;
67
using Xamarin.Android.Tools;
78

89
namespace MonoDroid.Generation {
@@ -23,19 +24,18 @@ internal static TextReader FieldXmlToCsv (string file)
2324
return null;
2425

2526
var sw = new StringWriter ();
26-
var doc = new XmlDocument ();
27-
doc.Load (file);
27+
var doc = XDocument.Load (file, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
2828

29-
foreach (XmlElement e in doc.SelectNodes ("/enum-field-mappings/mapping")) {
29+
foreach (var e in doc.XPathSelectElements ("/enum-field-mappings/mapping")) {
3030
string enu = GetMandatoryAttribute (e, "clr-enum-type");
31-
string jni_type = e.HasAttribute ("jni-class")
31+
string jni_type = e.Attribute ("jni-class") != null
3232
? e.XGetAttribute ("jni-class")
33-
: e.HasAttribute ("jni-interface")
33+
: e.Attribute ("jni-interface") != null
3434
? "I:" + e.XGetAttribute ("jni-interface")
3535
: GetMandatoryAttribute (e, "jni-class or jni-interface");
36-
bool bitfield = e.HasAttribute ("bitfield") && e.XGetAttribute ("bitfield") == "true";
37-
foreach (XmlElement m in e.SelectNodes ("field")) {
38-
string verstr = m.HasAttribute ("api-level")
36+
bool bitfield = e.Attribute ("bitfield") != null && e.XGetAttribute ("bitfield") == "true";
37+
foreach (var m in e.XPathSelectElements ("field")) {
38+
string verstr = m.Attribute ("api-level") != null
3939
? m.XGetAttribute ("api-level")
4040
: "0";
4141
string member = GetMandatoryAttribute (m, "clr-name");
@@ -48,10 +48,10 @@ internal static TextReader FieldXmlToCsv (string file)
4848
return new StringReader (sw.ToString ());
4949
}
5050

51-
static string GetMandatoryAttribute (XmlElement e, string name)
51+
static string GetMandatoryAttribute (XElement e, string name)
5252
{
53-
if (!e.HasAttribute (name)) {
54-
throw new InvalidOperationException (String.Format ("Mandatory attribute '{0}' is missing on a mapping element: {1}", name, e.OuterXml));
53+
if (e.Attribute (name) == null) {
54+
throw new InvalidOperationException (String.Format ("Mandatory attribute '{0}' is missing on a mapping element: {1}", name, e.ToString ()));
5555
}
5656
return e.XGetAttribute (name);
5757
}

src/utils/XmlExtensions.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22
using System.IO;
33
using System.Linq;
44
using System.Xml;
5+
using System.Xml.Linq;
56
using System.Xml.XPath;
67

78
namespace Xamarin.Android.Tools {
89

910
static class XmlExtensions {
1011

11-
public static string XGetAttribute (this XmlElement element, string name)
12+
public static string XGetAttribute (this XElement element, string name)
1213
{
13-
var attr = element.GetAttribute (name);
14-
return attr != null ? attr.Trim () : null;
14+
var attr = element.Attribute (name);
15+
return attr != null ? attr.Value.Trim () : null;
1516
}
1617

1718
public static string XGetAttribute (this XPathNavigator nav, string name, string ns)

tools/generator/ApiFixup.cs

Lines changed: 64 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -3,213 +3,192 @@
33
using System.Linq;
44
using System.Xml;
55
using System.Xml.XPath;
6+
using System.Xml.Linq;
67

78
using Xamarin.Android.Tools;
89

910
namespace MonoDroid.Generation
1011
{
1112
public class ApiFixup
1213
{
13-
XmlDocument api_doc;
14+
XDocument api_doc;
1415
string apiSource = "";
1516

1617
public string ApiSource { get { return apiSource; } }
1718

18-
public ApiFixup (XmlDocument apiDoc)
19+
public ApiFixup (XDocument apiDoc)
1920
{
2021
api_doc = apiDoc;
21-
var api = api_doc.DocumentElement;
22+
var api = api_doc.Root;
2223
if (api != null)
2324
apiSource = api.XGetAttribute ("api-source");
2425
}
2526

26-
public void Process (IEnumerable<XmlDocument> metaDocs, string apiLevel, int productVersion)
27+
public void Process (IEnumerable<XDocument> metaDocs, string apiLevel, int productVersion)
2728
{
2829
foreach (var metaDoc in metaDocs)
2930
Process (metaDoc, apiLevel, productVersion);
3031
}
3132

32-
bool ShouldSkip (XPathNavigator node, int apiLevel, int productVersion)
33+
bool ShouldSkip (XElement node, int apiLevel, int productVersion)
3334
{
3435
if (apiLevel > 0) {
35-
string apiSince = node.XGetAttribute ("api-since", "");
36-
string apiUntil = node.XGetAttribute ("api-until", "");
36+
string apiSince = node.XGetAttribute ("api-since");
37+
string apiUntil = node.XGetAttribute ("api-until");
3738
if (!string.IsNullOrEmpty (apiSince) && int.Parse (apiSince) > apiLevel)
3839
return true;
3940
if (!string.IsNullOrEmpty (apiUntil) && int.Parse (apiUntil) < apiLevel)
4041
return true;
4142
}
4243
if (productVersion > 0) {
43-
var product_version = node.XGetAttribute ("product-version", "");
44+
var product_version = node.XGetAttribute ("product-version");
4445
if (!string.IsNullOrEmpty (product_version) && int.Parse (product_version) > productVersion)
4546
return true;
4647
}
4748
return false;
4849
}
4950

50-
bool ShouldApply (XPathNavigator node)
51+
bool ShouldApply (XElement node)
5152
{
5253
if (!string.IsNullOrEmpty (apiSource)) {
53-
var targetsource = node.XGetAttribute ("api-source", "");
54+
var targetsource = node.XGetAttribute ("api-source");
5455
if (string.IsNullOrEmpty (targetsource))
5556
return true;
5657
return targetsource == apiSource;
5758
}
5859
return true;
5960
}
6061

61-
void Process (XmlDocument meta_doc, string apiLevelString, int productVersion)
62+
void Process (XDocument meta_doc, string apiLevelString, int productVersion)
6263
{
63-
XPathNavigator api_nav = api_doc.CreateNavigator ();
64-
XPathNavigator meta_nav = meta_doc.CreateNavigator ();
6564
int apiLevel = 0;
6665
int.TryParse (apiLevelString, out apiLevel);
6766

68-
XPathNodeIterator metadata = meta_nav.Select ("/metadata/*");
67+
var metadataChildren = meta_doc.XPathSelectElements ("/metadata/*");
6968
string prev_path = null;
70-
XPathNavigator attr_last_cache = null;
69+
XElement attr_last_cache = null;
7170

72-
while (metadata.MoveNext ()) {
73-
var metanav = metadata.Current;
74-
if (ShouldSkip (metanav, apiLevel, productVersion))
71+
foreach (var metaitem in metadataChildren) {
72+
if (ShouldSkip (metaitem, apiLevel, productVersion))
7573
continue;
76-
if (!ShouldApply (metanav))
74+
if (!ShouldApply (metaitem))
7775
continue;
78-
string path = metanav.XGetAttribute ("path", "");
76+
string path = metaitem.XGetAttribute ("path");
7977
if (path != prev_path)
8078
attr_last_cache = null;
8179
prev_path = path;
8280

83-
switch (metanav.LocalName) {
81+
switch (metaitem.Name.LocalName) {
8482
case "remove-node":
8583
try {
86-
XPathNodeIterator api_iter = api_nav.Select (path);
87-
List<XmlElement> matches = new List<XmlElement> ();
88-
while (api_iter.MoveNext ())
89-
matches.Add (((IHasXmlNode)api_iter.Current).GetNode () as XmlElement);
90-
foreach (XmlElement api_node in matches)
91-
api_node.ParentNode.RemoveChild (api_node);
92-
if (matches.Count == 0)
84+
var nodes = api_doc.XPathSelectElements (path).ToArray ();
85+
if (nodes.Any ())
86+
foreach (var node in nodes)
87+
node.Remove ();
88+
else
9389
// BG8A00
94-
Report.Warning (0, Report.WarningApiFixup + 0, "<remove-node path=\"{0}\"/> matched no nodes.", path);
90+
Report.Warning (0, Report.WarningApiFixup + 0, null, metaitem, "<remove-node path=\"{0}\"/> matched no nodes.", path);
9591
} catch (XPathException e) {
9692
// BG4A01
97-
Report.Error (Report.ErrorApiFixup + 1, e, "Invalid XPath specification: {0}", path);
93+
Report.Error (Report.ErrorApiFixup + 1, e, metaitem, "Invalid XPath specification: {0}", path);
9894
}
9995
break;
10096
case "add-node":
10197
try {
102-
XPathNodeIterator api_iter = api_nav.Select (path);
98+
var nodes = api_doc.XPathSelectElements (path);
10399
bool matched = false;
104-
while (api_iter.MoveNext ()) {
105-
XmlElement api_node = ((IHasXmlNode)api_iter.Current).GetNode () as XmlElement;
106-
foreach (XmlNode child in ((IHasXmlNode)metanav).GetNode().ChildNodes)
107-
api_node.AppendChild (api_doc.ImportNode (child, true));
100+
if (!nodes.Any ())
101+
// BG8A01
102+
Report.Warning (0, Report.WarningApiFixup + 1, null, metaitem, "<add-node path=\"{0}\"/> matched no nodes.", path);
103+
else {
104+
foreach (var node in nodes)
105+
node.Add (metaitem.Nodes ());
108106
matched = true;
109107
}
110-
if (!matched)
111-
// BG8A01
112-
Report.Warning (0, Report.WarningApiFixup + 1, "<add-node path=\"{0}\"/> matched no nodes.", path);
113108
} catch (XPathException e) {
114109
// BG4A02
115-
Report.Error (Report.ErrorApiFixup + 2, e, "Invalid XPath specification: {0}", path);
110+
Report.Error (Report.ErrorApiFixup + 2, e, metaitem, "Invalid XPath specification: {0}", path);
116111
}
117112
break;
118113
case "change-node":
119114
try {
120-
XPathNodeIterator api_iter = api_nav.Select (path);
115+
var nodes = api_doc.XPathSelectElements (path);
121116
bool matched = false;
122-
while (api_iter.MoveNext ()) {
123-
XmlElement node = ( (IHasXmlNode) api_iter.Current).GetNode () as XmlElement;
124-
XmlElement parent = node.ParentNode as XmlElement;
125-
XmlElement new_node = api_doc.CreateElement (metanav.Value);
126-
127-
foreach (XmlNode child in node.ChildNodes)
128-
new_node.AppendChild (child.Clone ());
129-
foreach (XmlAttribute attribute in node.Attributes)
130-
new_node.Attributes.Append ( (XmlAttribute) attribute.Clone ());
131-
132-
parent.ReplaceChild (new_node, node);
117+
foreach (var node in nodes) {
118+
var newChild = new XElement (metaitem.Value);
119+
newChild.Add (node.Attributes ());
120+
newChild.Add (node.Nodes ());
121+
node.ReplaceWith (newChild);
133122
matched = true;
134123
}
135124

136125
if (!matched)
137126
// BG8A03
138-
Report.Warning (0, Report.WarningApiFixup + 3, "<change-node-type path=\"{0}\"/> matched no nodes.", path);
127+
Report.Warning (0, Report.WarningApiFixup + 3, null, metaitem, "<change-node-type path=\"{0}\"/> matched no nodes.", path);
139128
} catch (XPathException e) {
140129
// BG4A03
141-
Report.Error (Report.ErrorApiFixup + 3, e, "Invalid XPath specification: {0}", path);
130+
Report.Error (Report.ErrorApiFixup + 3, e, metaitem, "Invalid XPath specification: {0}", path);
142131
}
143132
break;
144133
case "attr":
145134
try {
146-
string attr_name = metanav.XGetAttribute ("name", "");
135+
string attr_name = metaitem.XGetAttribute ("name");
147136
if (string.IsNullOrEmpty (attr_name))
148137
// BG4A07
149-
Report.Error (Report.ErrorApiFixup + 7, "Target attribute name is not specified for path: {0}", path);
150-
var nodes = attr_last_cache != null ?
151-
(IEnumerable<XPathNavigator>) new XPathNavigator [] {attr_last_cache} :
152-
api_nav.Select (path).OfType<XPathNavigator> ();
138+
Report.Error (Report.ErrorApiFixup + 7, null, metaitem, "Target attribute name is not specified for path: {0}", path);
139+
var nodes = attr_last_cache != null ? new XElement [] { attr_last_cache } : api_doc.XPathSelectElements (path);
153140
int attr_matched = 0;
154141
foreach (var n in nodes) {
155-
XmlElement node = ((IHasXmlNode) n).GetNode () as XmlElement;
156-
node.SetAttribute (attr_name, metanav.Value);
157-
//attr_last_cache = n;
142+
n.SetAttributeValue (attr_name, metaitem.Value);
158143
attr_matched++;
159144
}
160145
if (attr_matched == 0)
161146
// BG8A04
162-
Report.Warning (0, Report.WarningApiFixup + 4, "<attr path=\"{0}\"/> matched no nodes.", path);
147+
Report.Warning (0, Report.WarningApiFixup + 4, null, metaitem, "<attr path=\"{0}\"/> matched no nodes.", path);
163148
if (attr_matched != 1)
164149
attr_last_cache = null;
165150
} catch (XPathException e) {
166151
// BG4A04
167-
Report.Error (Report.ErrorApiFixup + 4, e, "Invalid XPath specification: {0}", path);
152+
Report.Error (Report.ErrorApiFixup + 4, e, metaitem, "Invalid XPath specification: {0}", path);
168153
}
169154
break;
170155
case "move-node":
171156
try {
172-
XPathExpression expr = api_nav.Compile (path);
173-
string parent = metanav.Value;
174-
XPathNodeIterator parent_iter = api_nav.Select (parent);
157+
string parent = metaitem.Value;
158+
var parents = api_doc.XPathSelectElements (parent);
175159
bool matched = false;
176-
while (parent_iter.MoveNext ()) {
177-
XmlNode parent_node = ((IHasXmlNode)parent_iter.Current).GetNode ();
178-
XPathNodeIterator path_iter = parent_iter.Current.Clone ().Select (expr);
179-
while (path_iter.MoveNext ()) {
180-
XmlNode node = ((IHasXmlNode)path_iter.Current).GetNode ();
181-
parent_node.AppendChild (node.Clone ());
182-
node.ParentNode.RemoveChild (node);
183-
}
160+
foreach (var parent_node in parents) {
161+
var nodes = parent_node.XPathSelectElements (path).ToArray ();
162+
foreach (var node in nodes)
163+
node.Remove ();
164+
parent_node.Add (nodes);
184165
matched = true;
185166
}
186167
if (!matched)
187168
// BG8A05
188-
Report.Warning (0, Report.WarningApiFixup + 5, "<move-node path=\"{0}\"/> matched no nodes.", path);
169+
Report.Warning (0, Report.WarningApiFixup + 5, null, metaitem, "<move-node path=\"{0}\"/> matched no nodes.", path);
189170
} catch (XPathException e) {
190171
// BG4A05
191-
Report.Error (Report.ErrorApiFixup + 5, e, "Invalid XPath specification: {0}", path);
172+
Report.Error (Report.ErrorApiFixup + 5, e, metaitem, "Invalid XPath specification: {0}", path);
192173
}
193174
break;
194175
case "remove-attr":
195176
try {
196-
string name = metanav.XGetAttribute ("name", "");
197-
XPathNodeIterator api_iter = api_nav.Select (path);
177+
string name = metaitem.XGetAttribute ("name");
178+
var nodes = api_doc.XPathSelectElements (path);
198179
bool matched = false;
199-
200-
while (api_iter.MoveNext ()) {
201-
XmlElement node = ( (IHasXmlNode) api_iter.Current).GetNode () as XmlElement;
202-
203-
node.RemoveAttribute (name);
180+
181+
foreach (var node in nodes) {
182+
node.RemoveAttributes ();
204183
matched = true;
205184
}
206185

207186
if (!matched)
208187
// BG8A06
209-
Report.Warning (0, Report.WarningApiFixup + 6, "<remove-attr path=\"{0}\"/> matched no nodes.", path);
188+
Report.Warning (0, Report.WarningApiFixup + 6, null, metaitem, "<remove-attr path=\"{0}\"/> matched no nodes.", path);
210189
} catch (XPathException e) {
211190
// BG4A06
212-
Report.Error (Report.ErrorApiFixup + 6, e, "Invalid XPath specification: {0}", path);
191+
Report.Error (Report.ErrorApiFixup + 6, e, metaitem, "Invalid XPath specification: {0}", path);
213192
}
214193
break;
215194
}

tools/generator/ClassGen.cs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Xamarin.Android.Tools;
1313

1414
using MonoDroid.Utils;
15+
using System.Xml.Linq;
1516

1617
namespace MonoDroid.Generation {
1718
#if USE_CECIL
@@ -75,26 +76,23 @@ public class XmlClassGen : ClassGen {
7576
bool is_final;
7677
string base_type;
7778

78-
public XmlClassGen (XmlElement pkg, XmlElement elem)
79+
public XmlClassGen (XElement pkg, XElement elem)
7980
: base (new XmlGenBaseSupport (pkg, elem))//FIXME: should not be xml specific
8081
{
8182
is_abstract = elem.XGetAttribute ("abstract") == "true";
8283
is_final = elem.XGetAttribute ("final") == "true";
8384
base_type = elem.XGetAttribute ("extends");
84-
foreach (XmlNode node in elem.ChildNodes) {
85-
XmlElement child = node as XmlElement;
86-
if (child == null)
87-
continue;
88-
switch (node.Name) {
85+
foreach (var child in elem.Elements ()) {
86+
switch (child.Name.LocalName) {
8987
case "implements":
9088
string iname = child.XGetAttribute ("name-generic-aware");
9189
iname = iname.Length > 0 ? iname : child.XGetAttribute ("name");
9290
AddInterface (iname);
9391
break;
9492
case "method":
95-
var synthetic = child.GetAttribute ("synthetic") == "true";
96-
var finalizer = child.GetAttribute ("name") == "finalize" &&
97-
child.GetAttribute ("jni-signature") == "()V";
93+
var synthetic = child.XGetAttribute ("synthetic") == "true";
94+
var finalizer = child.XGetAttribute ("name") == "finalize" &&
95+
child.XGetAttribute ("jni-signature") == "()V";
9896
if (!(synthetic || finalizer))
9997
AddMethod (new XmlMethod (this, child));
10098
break;
@@ -107,7 +105,7 @@ public XmlClassGen (XmlElement pkg, XmlElement elem)
107105
case "typeParameters":
108106
break; // handled at GenBaseSupport
109107
default:
110-
Report.Warning (0, Report.WarningClassGen + 1, "unexpected class child {0}.", node.Name);
108+
Report.Warning (0, Report.WarningClassGen + 1, "unexpected class child {0}.", child.Name);
111109
break;
112110
}
113111
}

0 commit comments

Comments
 (0)