├── .gitattributes
├── .gitignore
├── FirstFloor.Xcc.Test
├── ConditionTests.cs
├── FirstFloor.Xcc.Test.csproj
├── PageTests.cs
├── Properties
│ └── AssemblyInfo.cs
├── Xaml
│ ├── MyPage.WP81.Debug.RemoveIgnorableContent.expected.xaml
│ ├── MyPage.WP81.Debug.expected.xaml
│ ├── MyPage.WP81.Release.RemoveIgnorableContent.expected.xaml
│ ├── MyPage.WP81.Release.expected.xaml
│ ├── MyPage.Win81.Debug.RemoveIgnorableContent.expected.xaml
│ ├── MyPage.Win81.Debug.expected.xaml
│ ├── MyPage.Win81.Release.RemoveIgnorableContent.expected.xaml
│ ├── MyPage.Win81.Release.expected.xaml
│ ├── MyPage.xaml
│ ├── XamarinContentPage.android.expected.xaml
│ ├── XamarinContentPage.ios.expected.xaml
│ ├── XamarinContentPage.nosymbols.expected.xaml
│ ├── XamarinContentPage.wp.expected.xaml
│ └── XamarinContentPage.xaml
└── XmlTests.cs
├── FirstFloor.Xcc.sln
├── FirstFloor.Xcc
├── FirstFloor.Xcc.csproj
├── Nuget
│ ├── ReadMe.txt
│ └── Xcc.nuspec
├── PreprocessXaml.cs
├── Properties
│ └── AssemblyInfo.cs
├── Targets
│ ├── Xcc.Debug.targets
│ └── Xcc.targets
├── XamlPreprocessor.cs
└── Xmlns.cs
├── LICENSE.md
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sln text eol=crlf
2 | *.cs text eol=crlf
3 | *.xaml text eol=crlf
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ################################################################################
2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio.
3 | ################################################################################
4 |
5 | /FirstFloor.Xcc/bin/Debug
6 | /FirstFloor.Xcc/obj/Debug
7 | /FirstFloor.Xcc.Test/bin/Debug
8 | /FirstFloor.Xcc.Test/obj/Debug
9 | /.vs/FirstFloor.Xcc/v14/.suo
10 | /FirstFloor.Xcc/bin/Release
11 | /FirstFloor.Xcc/obj/Release
12 | /FirstFloor.Xcc.Test/bin/Release
13 | /FirstFloor.Xcc.Test/obj/Release
14 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/ConditionTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System.Globalization;
4 |
5 | namespace FirstFloor.Xcc.Test
6 | {
7 | [TestClass]
8 | public class ConditionTests
9 | {
10 | [TestMethod]
11 | public void TestButtonBackgroundWin81()
12 | {
13 | TestXaml("WINDOWS_APP",
14 | "",
15 | "");
16 | }
17 |
18 | [TestMethod]
19 | public void TestButtonBackgroundWP81()
20 | {
21 | TestXaml("WINDOWS_PHONE_APP",
22 | "",
23 | "");
24 | }
25 |
26 | [TestMethod]
27 | public void TestButtonBackgroundDebug()
28 | {
29 | TestXaml("DEBUG",
30 | "",
31 | "");
32 | }
33 |
34 | [TestMethod]
35 | public void TestButtonBackgroundRelease()
36 | {
37 | TestXaml("!DEBUG",
38 | "",
39 | "");
40 | }
41 |
42 | [TestMethod]
43 | public void TestNoUpdates()
44 | {
45 | TestXaml("WINDOWS_APP",
46 | "",
47 | "");
48 | }
49 |
50 | [TestMethod]
51 | public void TestGridWin81()
52 | {
53 | TestXaml("WINDOWS_APP",
54 | "",
55 | "");
56 | }
57 |
58 | [TestMethod]
59 | public void TestGridWP81()
60 | {
61 | TestXaml("WINDOWS_PHONE_APP",
62 | "",
63 | "");
64 | }
65 |
66 | [TestMethod]
67 | public void TestGridDebug()
68 | {
69 | TestXaml("DEBUG",
70 | "",
71 | "");
72 | }
73 |
74 | [TestMethod]
75 | public void TestGridRelease()
76 | {
77 | TestXaml("!DEBUG",
78 | "",
79 | "");
80 | }
81 |
82 | [TestMethod]
83 | public void TestGridContentWin81()
84 | {
85 | TestXaml("WINDOWS_APP",
86 | "",
87 | "");
88 | }
89 |
90 | [TestMethod]
91 | public void TestGridContentWP81()
92 | {
93 | TestXaml("WINDOWS_PHONE_APP",
94 | "",
95 | "");
96 | }
97 |
98 | [TestMethod]
99 | public void TestGridContentDebug()
100 | {
101 | TestXaml("DEBUG",
102 | "",
103 | "");
104 | }
105 |
106 | [TestMethod]
107 | public void TestGridContentRelease()
108 | {
109 | TestXaml("!DEBUG",
110 | "",
111 | "");
112 | }
113 |
114 | [TestMethod]
115 | public void TestGridsWin81Debug()
116 | {
117 | TestXaml("WINDOWS_APP;DEBUG",
118 | "",
119 | "");
120 | }
121 |
122 | [TestMethod]
123 | public void TestGridsWin81Release()
124 | {
125 | TestXaml("WINDOWS_APP;!DEBUG",
126 | "",
127 | "");
128 | }
129 |
130 | [TestMethod]
131 | public void TestGridsWP81Debug()
132 | {
133 | TestXaml("WINDOWS_PHONE_APP;DEBUG",
134 | "",
135 | "");
136 | }
137 |
138 | [TestMethod]
139 | public void TestGridsWP81Release()
140 | {
141 | TestXaml("WINDOWS_PHONE_APP;!DEBUG",
142 | "",
143 | "");
144 | }
145 |
146 | [TestMethod]
147 | public void TestNestedWin81Debug()
148 | {
149 | TestXaml("WINDOWS_APP;DEBUG",
150 | "",
151 | "");
152 | }
153 |
154 | [TestMethod]
155 | public void TestNestedWP81Debug()
156 | {
157 | TestXaml("WINDOWS_PHONE_APP;DEBUG",
158 | "",
159 | "");
160 | }
161 |
162 | [TestMethod]
163 | public void TestNestedWin81Release()
164 | {
165 | TestXaml("WINDOWS_APP;!DEBUG",
166 | "",
167 | "");
168 | }
169 |
170 | [TestMethod]
171 | public void TestNestedWP81Release()
172 | {
173 | TestXaml("WINDOWS_PHONE_APP;!DEBUG",
174 | "",
175 | "");
176 | }
177 |
178 | [TestMethod]
179 | public void TestNestedAttributeWin81Debug()
180 | {
181 | TestXaml("WINDOWS_APP;DEBUG",
182 | "",
183 | "");
184 | }
185 |
186 | [TestMethod]
187 | public void TestNestedAttributeWP81Debug()
188 | {
189 | TestXaml("WINDOWS_PHONE_APP;DEBUG",
190 | "",
191 | "");
192 | }
193 |
194 | [TestMethod]
195 | public void TestNestedAttributeWin81Release()
196 | {
197 | TestXaml("WINDOWS_APP;!DEBUG",
198 | "",
199 | "");
200 | }
201 |
202 | [TestMethod]
203 | public void TestNestedAttributeWP81Release()
204 | {
205 | TestXaml("WINDOWS_PHONE_APP;DEBUG",
206 | "",
207 | "");
208 | }
209 | [TestMethod]
210 | public void TestAdControlWin81Debug()
211 | {
212 | TestXaml("WINDOWS_APP;DEBUG",
213 | @"
214 |
215 |
216 |
217 |
218 |
219 | ",
220 | @"
221 |
222 |
223 |
224 | ");
225 | }
226 |
227 | [TestMethod]
228 | public void TestAdControlWP81Debug()
229 | {
230 | TestXaml("WINDOWS_PHONE_APP;DEBUG",
231 | @"
232 |
233 |
234 |
235 |
236 |
237 | ",
238 | @"
239 |
240 |
241 |
242 |
243 |
244 | ");
245 | }
246 |
247 | private static void TestXaml(string symbols, string xamlSnippet, string expectedResult)
248 | {
249 | var preprocessor = new XamlPreprocessor(symbols, false);
250 | var xaml = CreateInputXamlPage(xamlSnippet);
251 | var result = preprocessor.ProcessXaml(xaml);
252 |
253 | Assert.AreEqual(CreateResultXamlPage(expectedResult), result);
254 | }
255 |
256 | private static string CreateInputXamlPage(string snippet)
257 | {
258 | return string.Format(CultureInfo.InvariantCulture, @"
259 |
260 | {0}
261 |
262 | ", snippet);
263 | }
264 |
265 | private static string CreateResultXamlPage(string snippet)
266 | {
267 | return string.Format(CultureInfo.InvariantCulture, @"
268 |
269 | {0}
270 |
271 | ", snippet);
272 | }
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/FirstFloor.Xcc.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {F1E02C83-C663-4F7A-BFB4-38C1D789C806}
7 | Library
8 | Properties
9 | FirstFloor.Xcc.Test
10 | FirstFloor.Xcc.Test
11 | v4.5
12 | 512
13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 10.0
15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
17 | False
18 | UnitTest
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | true
30 | full
31 | false
32 | bin\Debug\
33 | DEBUG;TRACE
34 | prompt
35 | 4
36 |
37 |
38 | pdbonly
39 | true
40 | bin\Release\
41 | TRACE
42 | prompt
43 | 4
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | {676f5ae5-48b2-4017-9a98-3bf571ebbd7b}
70 | FirstFloor.Xcc
71 |
72 |
73 |
74 |
75 | MSBuild:Compile
76 | Designer
77 |
78 |
79 | Designer
80 | MSBuild:Compile
81 |
82 |
83 | Designer
84 | MSBuild:Compile
85 |
86 |
87 |
88 |
89 | MSBuild:Compile
90 | Designer
91 |
92 |
93 |
94 |
95 | MSBuild:Compile
96 | Designer
97 |
98 |
99 |
100 |
101 | MSBuild:Compile
102 | Designer
103 |
104 |
105 |
106 |
107 | MSBuild:Compile
108 | Designer
109 |
110 |
111 |
112 |
113 | MSBuild:Compile
114 | Designer
115 |
116 |
117 |
118 |
119 | MSBuild:Compile
120 | Designer
121 |
122 |
123 |
124 |
125 | MSBuild:Compile
126 | Designer
127 |
128 |
129 |
130 |
131 | MSBuild:Compile
132 | Designer
133 |
134 |
135 |
136 |
137 | MSBuild:Compile
138 | Designer
139 |
140 |
141 |
142 |
143 | MSBuild:Compile
144 | Designer
145 |
146 |
147 |
148 |
149 | MSBuild:Compile
150 | Designer
151 |
152 |
153 |
154 |
155 |
156 |
157 | False
158 |
159 |
160 | False
161 |
162 |
163 | False
164 |
165 |
166 | False
167 |
168 |
169 |
170 |
171 |
172 |
173 |
180 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/PageTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System.IO;
4 | using System.Globalization;
5 |
6 | namespace FirstFloor.Xcc.Test
7 | {
8 | [TestClass]
9 | public class PageTests
10 | {
11 | [TestMethod]
12 | public void TestMyPageWin81Debug()
13 | {
14 | TestXaml("WINDOWS_APP;DEBUG", false, "MyPage.xaml", "MyPage.Win81.Debug.expected.xaml");
15 | }
16 |
17 | [TestMethod]
18 | public void TestMyPageWin81DebugRemoveIgnorableContent()
19 | {
20 | TestXaml("WINDOWS_APP;DEBUG", true, "MyPage.xaml", "MyPage.Win81.Debug.RemoveIgnorableContent.expected.xaml");
21 | }
22 |
23 | [TestMethod]
24 | public void TestMyPageWP81Debug()
25 | {
26 | TestXaml("WINDOWS_PHONE_APP;DEBUG", false, "MyPage.xaml", "MyPage.WP81.Debug.expected.xaml");
27 | }
28 |
29 | [TestMethod]
30 | public void TestMyPageWP81DebugRemoveIgnorableContent()
31 | {
32 | TestXaml("WINDOWS_PHONE_APP;DEBUG", true, "MyPage.xaml", "MyPage.WP81.Debug.RemoveIgnorableContent.expected.xaml");
33 | }
34 |
35 | [TestMethod]
36 | public void TestMyPageWin81Release()
37 | {
38 | TestXaml("WINDOWS_APP;!DEBUG", false, "MyPage.xaml", "MyPage.Win81.Release.expected.xaml");
39 | }
40 |
41 | [TestMethod]
42 | public void TestMyPageWin81ReleaseRemoveIgnorableContent()
43 | {
44 | TestXaml("WINDOWS_APP;!DEBUG", true, "MyPage.xaml", "MyPage.Win81.Release.RemoveIgnorableContent.expected.xaml");
45 | }
46 |
47 | [TestMethod]
48 | public void TestMyPageWP81Release()
49 | {
50 | TestXaml("WINDOWS_PHONE_APP;!DEBUG", false, "MyPage.xaml", "MyPage.WP81.Release.expected.xaml");
51 | }
52 |
53 | [TestMethod]
54 | public void TestMyPageWP81ReleaseRemoveIgnorableContent()
55 | {
56 | TestXaml("WINDOWS_PHONE_APP;!DEBUG", true, "MyPage.xaml", "MyPage.WP81.Release.RemoveIgnorableContent.expected.xaml");
57 | }
58 |
59 | [TestMethod]
60 | public void TestXamarinContentPageAndroid()
61 | {
62 | TestXaml("__ANDROID__", false, "XamarinContentPage.xaml", "XamarinContentPage.android.expected.xaml");
63 | }
64 |
65 | [TestMethod]
66 | public void TestXamarinContentPageAndroidRemoveIgnorableContent()
67 | {
68 | TestXaml("__ANDROID__", true, "XamarinContentPage.xaml", "XamarinContentPage.android.expected.xaml");
69 | }
70 |
71 | [TestMethod]
72 | public void TestXamarinContentPageiOs()
73 | {
74 | TestXaml("__IOS__", false, "XamarinContentPage.xaml", "XamarinContentPage.ios.expected.xaml");
75 | }
76 |
77 | [TestMethod]
78 | public void TestXamarinContentPageiOsRemoveIgnorableContent()
79 | {
80 | TestXaml("__IOS__", true, "XamarinContentPage.xaml", "XamarinContentPage.ios.expected.xaml");
81 | }
82 |
83 | [TestMethod]
84 | public void TestXamarinContentPageWP()
85 | {
86 | TestXaml("WINDOWS_PHONE", false, "XamarinContentPage.xaml", "XamarinContentPage.wp.expected.xaml");
87 | }
88 |
89 | [TestMethod]
90 | public void TestXamarinContentPageWPRemoveIgnorableContent()
91 | {
92 | TestXaml("WINDOWS_PHONE", true, "XamarinContentPage.xaml", "XamarinContentPage.wp.expected.xaml");
93 | }
94 |
95 | [TestMethod]
96 | public void TestXamarinContentPageNoSymbols()
97 | {
98 | TestXaml(null, false, "XamarinContentPage.xaml", "XamarinContentPage.nosymbols.expected.xaml");
99 | }
100 |
101 | [TestMethod]
102 | public void TestXamarinContentPageNoSymbolsRemoveIgnorableContent()
103 | {
104 | TestXaml(null, true, "XamarinContentPage.xaml", "XamarinContentPage.nosymbols.expected.xaml");
105 | }
106 |
107 | private static void TestXaml(string symbols, bool removeIgnorableContent, string xamlName, string expectedXamlName)
108 | {
109 | var preprocessor = new XamlPreprocessor(symbols, removeIgnorableContent);
110 | var xaml = LoadXamlPage(xamlName);
111 | var expected = LoadXamlPage(expectedXamlName);
112 | var result = preprocessor.ProcessXaml(xaml);
113 |
114 | // perform char-by-char comparison, raise error with index info if mismatch
115 | var lineNumber = 1;
116 | for (var i = 0; i < expected.Length && i < result.Length; i++) {
117 | if (expected[i] != result[i]) {
118 | Assert.Fail("Character mismatch at index {0} (line number: {1}. Expected: {2} ({3}), actual: {4} ({5})", i, lineNumber, result[i], (int)result[i], expected[i], (int)expected[i]);
119 | }
120 | if (result[i] == '\n') {
121 | lineNumber++;
122 | }
123 | }
124 |
125 | // still fail if one string is substring of the other
126 | Assert.AreEqual(expected, result);
127 | }
128 |
129 | private static string LoadXamlPage(string pageName)
130 | {
131 | var fullName = string.Format(CultureInfo.InvariantCulture, "FirstFloor.Xcc.Test.Xaml.{0}", pageName);
132 |
133 | using (var stream = typeof(PageTests).Assembly.GetManifestResourceStream(fullName)) {
134 | using (var reader = new StreamReader(stream)) {
135 | return reader.ReadToEnd();
136 | }
137 | }
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("FirstFloor.Xcc.Test")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("FirstFloor.Xcc.Test")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("5a479e3f-e50f-4bbe-99a6-b569f5b207b4")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/MyPage.WP81.Debug.RemoveIgnorableContent.expected.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/MyPage.WP81.Debug.expected.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/MyPage.WP81.Release.RemoveIgnorableContent.expected.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/MyPage.WP81.Release.expected.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/MyPage.Win81.Debug.RemoveIgnorableContent.expected.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/MyPage.Win81.Debug.expected.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/MyPage.Win81.Release.RemoveIgnorableContent.expected.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/MyPage.Win81.Release.expected.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/MyPage.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/XamarinContentPage.android.expected.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/XamarinContentPage.ios.expected.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/XamarinContentPage.nosymbols.expected.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/XamarinContentPage.wp.expected.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/Xaml/XamarinContentPage.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.Test/XmlTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace FirstFloor.Xcc.Test
9 | {
10 | [TestClass]
11 | public class XmlTests
12 | {
13 | [TestMethod]
14 | public void TestInputShouldNotChange()
15 | {
16 | var xml = @"
17 |
20 |
21 |
22 | ";
23 | TestXml(null, false, xml, xml);
24 | }
25 |
26 | [TestMethod]
27 | public void TestRemoveIgnorableContent()
28 | {
29 | var xml = @"
30 |
33 |
34 |
35 | ";
36 | var expected = @"
37 |
38 |
39 |
40 |
41 |
42 | ";
43 |
44 | TestXml(null, true, xml, expected);
45 | }
46 |
47 | [TestMethod]
48 | public void TestRootNotInDefaultNamespace()
49 | {
50 | var xml = @"
51 |
57 |
58 |
59 | ";
60 | var expected = @"
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | ";
70 |
71 | TestXml("DEBUG", false, xml, expected);
72 | }
73 |
74 | [TestMethod]
75 | public void TestRootNotInDefaultNamespaceWithNoDefaultNamespace()
76 | {
77 | var xml = @"
78 |
83 |
84 |
85 | ";
86 | var expected = @"
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | ";
95 |
96 | TestXml("DEBUG", false, xml, expected);
97 | }
98 |
99 | [TestMethod]
100 | public void TestRootNotInDefaultNamespaceWithDefaultNamespaceRemoveIgnorableContent()
101 | {
102 | var xml = @"
103 |
109 |
110 |
111 | ";
112 | var expected = @"
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | ";
122 |
123 | TestXml("DEBUG", true, xml, expected);
124 | }
125 |
126 | [TestMethod]
127 | public void TestRootNotInDefaultNamespaceWithNoDefaultNamespaceRemoveIgnorableContent()
128 | {
129 | var xml = @"
130 |
135 |
136 |
137 | ";
138 | var expected = @"
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | ";
147 |
148 | TestXml("DEBUG", true, xml, expected);
149 | }
150 |
151 | private static void TestXml(string symbols, bool removeIgnorableContent, string xaml, string expected)
152 | {
153 | var preprocessor = new XamlPreprocessor(symbols, removeIgnorableContent);
154 | var result = preprocessor.ProcessXaml(xaml);
155 |
156 | Assert.AreEqual(expected, result);
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.30324.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FirstFloor.Xcc", "FirstFloor.Xcc\FirstFloor.Xcc.csproj", "{676F5AE5-48B2-4017-9A98-3BF571EBBD7B}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FirstFloor.Xcc.Test", "FirstFloor.Xcc.Test\FirstFloor.Xcc.Test.csproj", "{F1E02C83-C663-4F7A-BFB4-38C1D789C806}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {676F5AE5-48B2-4017-9A98-3BF571EBBD7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {676F5AE5-48B2-4017-9A98-3BF571EBBD7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {676F5AE5-48B2-4017-9A98-3BF571EBBD7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {676F5AE5-48B2-4017-9A98-3BF571EBBD7B}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {F1E02C83-C663-4F7A-BFB4-38C1D789C806}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {F1E02C83-C663-4F7A-BFB4-38C1D789C806}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {F1E02C83-C663-4F7A-BFB4-38C1D789C806}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {F1E02C83-C663-4F7A-BFB4-38C1D789C806}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc/FirstFloor.Xcc.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {676F5AE5-48B2-4017-9A98-3BF571EBBD7B}
8 | Library
9 | Properties
10 | FirstFloor.Xcc
11 | Xcc
12 | v4.5
13 | 512
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | true
25 | full
26 | false
27 | bin\Debug\
28 | DEBUG;TRACE
29 | prompt
30 | 4
31 | true
32 | bin\Debug\Xcc.XML
33 |
34 |
35 | pdbonly
36 | true
37 | bin\Release\
38 | TRACE
39 | prompt
40 | 4
41 | true
42 | bin\Release\Xcc.XML
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
78 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc/Nuget/ReadMe.txt:
--------------------------------------------------------------------------------
1 | XAML CONDITIONAL COMPILATION
2 | ============================
3 | Welcome to XCC, a preprocessor adding conditional compilation support to XAML files.
4 |
5 | See https://github.com/firstfloorsoftware/xcc for instructions
--------------------------------------------------------------------------------
/FirstFloor.Xcc/Nuget/Xcc.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | xcc
5 | 1.0.9
6 | XAML Conditional Compilation
7 | First Floor Software
8 | First Floor Software
9 | https://github.com/firstfloorsoftware/xcc
10 | false
11 | A XAML preprocessor adding conditional compilation support to XAML files
12 | 1.0.9: Fixes incorrect SHA256 nupkg hash
13 | 1.0.8: Fixed failing target invocation in Xamarin projects
14 | 1.0.7: Added configurable support for removing ignorable content
15 | 1.0.6: Fixed conditional elements moved to incorrect xmlns
16 | 1.0.5: Fixed Xamarin Forms precompile step not being executed
17 | 1.0.4: Added support for WPF projects
18 | 1.0.3: Added support for Visual C++ projects
19 | 1.0.2: Added support for Xamarin Forms
20 | 1.0.1: Fixed trailing content bug
21 |
22 | Copyright 2014-2016 First Floor Software
23 | XAML, preprocessor, conditional compilation
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc/PreprocessXaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Build.Framework;
2 | using Microsoft.Build.Utilities;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Xml.Linq;
9 |
10 | namespace FirstFloor.Xcc
11 | {
12 | ///
13 | /// The MSBuild task for preprocessing conditional compilation symbols in XAML files.
14 | ///
15 | public class PreprocessXaml
16 | : Task
17 | {
18 | ///
19 | /// The required DefinedSymbols parameter.
20 | ///
21 | [Required]
22 | public string DefinedSymbols { get; set; }
23 | ///
24 | /// The required ApplicationDefinitions parameter.
25 | ///
26 | [Required]
27 | public ITaskItem[] ApplicationDefinitions { get; set; }
28 | ///
29 | /// The required Pages parameter.
30 | ///
31 | [Required]
32 | public ITaskItem[] Pages { get; set; }
33 | ///
34 | /// The required EmbeddedXamlResources parameter.
35 | ///
36 | [Required]
37 | public ITaskItem[] EmbeddedXamlResources { get; set; }
38 | ///
39 | /// The required OutputPath parameter.
40 | ///
41 | [Required]
42 | public string OutputPath { get; set; }
43 | ///
44 | /// Determines whether ignorable content should be removed.
45 | ///
46 | ///
47 | /// true if [remove ignorable content]; otherwise, false.
48 | ///
49 | public bool RemoveIgnorableContent { get; set; }
50 |
51 | ///
52 | /// The output NewApplicationDefinitions parameter.
53 | ///
54 | [Output]
55 | public ITaskItem[] NewApplicationDefinitions { get; set; }
56 | ///
57 | /// The output NewPages parameter.
58 | ///
59 | [Output]
60 | public ITaskItem[] NewPages { get; set; }
61 | ///
62 | /// The output NewEmbeddedXamlResources parameter.
63 | ///
64 | [Output]
65 | public ITaskItem[] NewEmbeddedXamlResources { get; set; }
66 | ///
67 | /// The output GeneratedFiles parameter.
68 | ///
69 | [Output]
70 | public ITaskItem[] GeneratedFiles { get; set; }
71 |
72 | ///
73 | /// When overridden in a derived class, executes the task.
74 | ///
75 | ///
76 | /// true if the task successfully executed; otherwise, false.
77 | ///
78 | public override bool Execute()
79 | {
80 | try {
81 | Log.LogMessage(MessageImportance.Normal, "XCC > DefinedSymbols: {0}", string.Join(",", this.DefinedSymbols));
82 |
83 | var preprocessor = new XamlPreprocessor(this.DefinedSymbols, this.RemoveIgnorableContent);
84 |
85 | var generatedFiles = new List();
86 |
87 | this.NewApplicationDefinitions = ProcessFiles(this.ApplicationDefinitions, generatedFiles, preprocessor).ToArray();
88 | this.NewPages = ProcessFiles(this.Pages, generatedFiles, preprocessor).ToArray();
89 | this.NewEmbeddedXamlResources = ProcessFiles(this.EmbeddedXamlResources, generatedFiles, preprocessor).ToArray();
90 |
91 | this.GeneratedFiles = generatedFiles.ToArray();
92 |
93 | return true;
94 | }
95 | catch (Exception e) {
96 | Log.LogErrorFromException(e);
97 |
98 | return false;
99 | }
100 | }
101 |
102 | private IEnumerable ProcessFiles(ITaskItem[] files, List generatedFiles, XamlPreprocessor preprocessor)
103 | {
104 | foreach (var file in files) {
105 | var newFile = ProcessFile(file, preprocessor);
106 | if (newFile != null) {
107 | generatedFiles.Add(newFile);
108 | yield return newFile;
109 | }
110 | else {
111 | yield return file; // return file as-is
112 | }
113 | }
114 | }
115 |
116 | private ITaskItem ProcessFile(ITaskItem file, XamlPreprocessor preprocessor)
117 | {
118 | var sourcePath = file.GetMetadata("FullPath");
119 |
120 | // properly resolve linked xaml
121 | var targetRelativePath = file.GetMetadata("Link");
122 | if (string.IsNullOrEmpty(targetRelativePath)) {
123 | targetRelativePath = file.ItemSpec;
124 | }
125 |
126 | // if targetRelativePath is still absolute, use file name
127 | if (Path.IsPathRooted(targetRelativePath)) {
128 | targetRelativePath = Path.GetFileName(targetRelativePath);
129 | }
130 |
131 | var targetPath = Path.Combine(this.OutputPath, targetRelativePath);
132 |
133 | TaskItem result = null;
134 |
135 | // process XAML
136 | Log.LogMessage(MessageImportance.High, "XCC > Preprocessing {0}", targetRelativePath);
137 | var start = DateTime.Now;
138 | if (preprocessor.ProcessXamlFile(sourcePath, targetPath)) {
139 | // targetPath has been written, create linked item
140 | result = new TaskItem(targetPath);
141 | file.CopyMetadataTo(result);
142 | result.SetMetadata("Link", targetRelativePath); // this is the trick that makes it all work (replace page with a page link pointing to \obj\debug\preprocessedxaml\*)
143 | }
144 |
145 | var duration = (DateTime.Now - start).TotalMilliseconds;
146 | Log.LogMessage(MessageImportance.Normal, "XCC > Preprocess completed in {0}ms, {1} has {2}changed", duration, targetRelativePath, result == null ? "not " : "");
147 |
148 | return result;
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Xcc")]
9 | [assembly: AssemblyDescription("XAML Conditional Compilation")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("First Floor Software")]
12 | [assembly: AssemblyProduct("Xcc")]
13 | [assembly: AssemblyCopyright("Copyright © First Floor Software 2014-2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("1054bd1d-1a45-47f5-93b7-77882185e437")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.9.0")]
36 | [assembly: AssemblyFileVersion("1.0.9.0")]
37 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc/Targets/Xcc.Debug.targets:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | $(DefineConstants)
24 |
25 |
27 | XCC
28 | $(PreprocessorDefinitions);XCC
29 |
30 |
31 | %(ClCompile.PreprocessorDefinitions)
32 |
33 |
34 |
35 |
36 |
37 | __ANDROID__;$(DefineConstants)
38 |
39 |
40 | __ANDROID__;$(PreprocessorDefinitions)
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc/Targets/Xcc.targets:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | $(DefineConstants)
24 |
25 |
27 | XCC
28 | $(PreprocessorDefinitions);XCC
29 |
30 |
31 | %(ClCompile.PreprocessorDefinitions)
32 |
33 |
34 |
35 |
36 | __ANDROID__;$(DefineConstants)
37 |
38 |
39 | __ANDROID__;$(PreprocessorDefinitions)
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc/XamlPreprocessor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Xml;
9 | using System.Xml.Linq;
10 |
11 | namespace FirstFloor.Xcc
12 | {
13 | ///
14 | /// The actual XAML preprocessor
15 | ///
16 | public class XamlPreprocessor
17 | {
18 | private static readonly XName LineNumberName = "__line";
19 |
20 | private string[] definedSymbols;
21 | private bool removeIgnorableContent;
22 | private Dictionary conditionResults = new Dictionary();
23 |
24 | ///
25 | /// Initializes a new instance of the class.
26 | ///
27 | /// The defined symbols.
28 | /// Whether to remove ignorable content.
29 | public XamlPreprocessor(string definedSymbols, bool removeIgnorableContent)
30 | {
31 | this.definedSymbols = (definedSymbols ?? string.Empty).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
32 | this.removeIgnorableContent = removeIgnorableContent;
33 | }
34 |
35 | ///
36 | /// Processes the specified source XAML file and writes the results to specified target path.
37 | ///
38 | /// The source path.
39 | /// The target path.
40 | /// A value indicating whether the target XAML file has been written. If no changes are made to the XAML, the targetPath is not written and false is returned.
41 | public bool ProcessXamlFile(string sourcePath, string targetPath)
42 | {
43 | var xamlDoc = XDocument.Load(sourcePath, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
44 | var result = ProcessXaml(xamlDoc);
45 | if (result != null) {
46 | // ensure target directory exists
47 | Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
48 |
49 | using (var stream = File.Create(targetPath)) {
50 | SaveDocument(result, stream);
51 | }
52 | return true;
53 | }
54 | return false;
55 | }
56 |
57 | ///
58 | /// Processes the specified source XAML and returns the result.
59 | ///
60 | /// The xaml.
61 | ///
62 | public string ProcessXaml(string xaml)
63 | {
64 | var xamlDoc = XDocument.Parse(xaml, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
65 | var result = ProcessXaml(xamlDoc);
66 | if (result == null) {
67 | return xaml; // no changes, return XAML as-is
68 | }
69 |
70 | using (var stream = new MemoryStream()) {
71 | SaveDocument(result, stream);
72 |
73 | stream.Seek(0, SeekOrigin.Begin);
74 | using (var reader = new StreamReader(stream)) {
75 | return reader.ReadToEnd();
76 | }
77 | }
78 | }
79 |
80 | private static void SaveDocument(XDocument doc, Stream output)
81 | {
82 | var settings = new XmlWriterSettings {
83 | Indent = false,
84 | OmitXmlDeclaration = true
85 | };
86 |
87 | // do not dispose writer, this will close stream
88 | var writer = XmlWriter.Create(output, settings);
89 | doc.Save(writer);
90 | writer.Flush();
91 | }
92 |
93 | private XDocument ProcessXaml(XDocument xamlDoc)
94 | {
95 | // indicates whether the XAML has been updated
96 | var updated = false;
97 |
98 | // find the ignorable prefixes defined in optional mc:Ignorable
99 | var ignorableAttribute = xamlDoc.Root.Attribute(XName.Get("Ignorable", Xmlns.MarkupCompatibility));
100 | string[] ignorablePrefixes = null;
101 | if (ignorableAttribute != null) {
102 | ignorablePrefixes = ignorableAttribute.Value.Split(new char[0], StringSplitOptions.RemoveEmptyEntries); //char[0] defaults to any whitespace
103 | }
104 |
105 | // lookup ignorable namespace names (excluding condition namespaces) that should be removed
106 | var removeNamespaceNames = new string[0];
107 | if (this.removeIgnorableContent && ignorablePrefixes != null) {
108 | removeNamespaceNames = (from a in xamlDoc.Root.Attributes()
109 | where a.IsNamespaceDeclaration && !IsCondition(a.Value) && ignorablePrefixes.Contains(a.Name.LocalName)
110 | select a.Value).ToArray();
111 | }
112 |
113 | // using a Stack rather than relatively slow recursion
114 | var stack = new Stack();
115 | stack.Push(xamlDoc.Root);
116 |
117 | while (stack.Count > 0) {
118 | var element = stack.Pop();
119 | bool elementUpdated;
120 | if (ProcessElement(element, removeNamespaceNames, out elementUpdated)) {
121 | foreach (var e in element.Elements()) {
122 | stack.Push(e);
123 | }
124 | }
125 | if (elementUpdated) {
126 | updated = true;
127 | }
128 | }
129 |
130 | // clear markup compatibility and condition xmlns attributes from root
131 | // * WinRT XBF compiler doesn't appreciate mc:ProcessContent
132 | // * Xamarin Forms crashes on custom condition namespaces
133 | var removedPrefixes = new List();
134 | foreach (var attr in from a in xamlDoc.Root.Attributes().ToArray() // ToArray since we are modifying the attribute collection
135 | where a.Name == XName.Get("ProcessContent", Xmlns.MarkupCompatibility) || (a.IsNamespaceDeclaration && (IsCondition(a.Value) || removeNamespaceNames.Contains(a.Value)))
136 | select a) {
137 | attr.Remove();
138 |
139 | if (attr.IsNamespaceDeclaration) {
140 | removedPrefixes.Add(attr.Name.LocalName);
141 | }
142 |
143 | updated = true;
144 | }
145 |
146 | if (this.removeIgnorableContent) {
147 | if (ignorableAttribute != null) {
148 | // remove mc:Ignorable attribute entirely
149 | ignorableAttribute.Remove();
150 | updated = true;
151 | }
152 |
153 | // both mc:ProcessContent and mc:Ignorable have been removed, remove xmlns:mc="" as well
154 | var mcXmlnsAttribute = xamlDoc.Root.Attributes().FirstOrDefault(a => a.IsNamespaceDeclaration && a.Value == Xmlns.MarkupCompatibility);
155 | if (mcXmlnsAttribute != null) {
156 | mcXmlnsAttribute.Remove();
157 | updated = true;
158 | }
159 | }
160 | else if (removedPrefixes.Any()) {
161 | // update existing mc:Ignorable accordingly
162 | if (ignorableAttribute != null) {
163 | ignorableAttribute.Value = string.Join(" ", from p in ignorablePrefixes
164 | where !removedPrefixes.Contains(p)
165 | select p);
166 |
167 | // remove mc:Ignorable if value is empty
168 | if (string.IsNullOrEmpty(ignorableAttribute.Value)) {
169 | ignorableAttribute.Remove();
170 | }
171 | }
172 | }
173 |
174 | if (updated) {
175 | // re-read xdocument and adjust xelements so they are on original line numbers
176 | return AdjustLineNumbers(xamlDoc);
177 |
178 | }
179 | // not updated
180 | return null;
181 | }
182 |
183 | private static bool IsWhitespace(XNode node)
184 | {
185 | if (node.NodeType == XmlNodeType.Whitespace || node.NodeType == XmlNodeType.SignificantWhitespace) {
186 | return true;
187 | }
188 | var text = node as XText;
189 | return text != null && text.Value.Trim().Length == 0;
190 | }
191 |
192 | ///
193 | /// Returns an XDocument where the XElement instances are
194 | /// positioned at the exact same line number as the original xml source.
195 | ///
196 | ///
197 | ///
198 | private static XDocument AdjustLineNumbers(XDocument input)
199 | {
200 | XDocument result;
201 | // save input to stream and re-read
202 | using (var stream = new MemoryStream()) {
203 | input.Save(stream, SaveOptions.DisableFormatting);
204 |
205 | stream.Seek(0, SeekOrigin.Begin);
206 |
207 | // read serialized xml
208 | result = XDocument.Load(stream, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
209 | }
210 |
211 | // find line info on each element and insert whitespace where needed
212 | var offset = 0;
213 | foreach (var element in result.Descendants()) {
214 | var lineInfo = (IXmlLineInfo)element;
215 |
216 | var originalLineAttr = element.Attribute(LineNumberName);
217 | if (originalLineAttr == null) {
218 | continue;
219 | }
220 | int originalLineNumber;
221 | if (int.TryParse((string)originalLineAttr, NumberStyles.Integer, CultureInfo.InvariantCulture, out originalLineNumber)) {
222 | if (originalLineNumber > lineInfo.LineNumber + offset) {
223 | var count = originalLineNumber - lineInfo.LineNumber - offset;
224 | var whitespace = new XText(string.Join(Environment.NewLine, new string[count + 1]));
225 | offset += count;
226 |
227 | // insert newlines before whitespace of element
228 | XNode node = element;
229 | while (node.PreviousNode != null && IsWhitespace(node.PreviousNode)) {
230 | node = node.PreviousNode;
231 | }
232 | node.AddBeforeSelf(whitespace);
233 | }
234 | }
235 | // and remove line number attribute
236 | originalLineAttr.Remove();
237 | }
238 |
239 | return result;
240 | }
241 |
242 | private bool ProcessElement(XElement element, string[] removeNamespaceNames, out bool updated)
243 | {
244 | updated = false;
245 |
246 | // check if element should be included
247 | var elemMatch = Include(element.Name, removeNamespaceNames);
248 | if (elemMatch.HasValue) {
249 | updated = true;
250 |
251 | if (elemMatch.Value) {
252 | // move element to default namespace, if not found fall-back to root namespace
253 | var root = element.Document.Root;
254 | var defaultXmlns = root.Attributes("xmlns").FirstOrDefault();
255 | var nsName = defaultXmlns != null ? defaultXmlns.Value : root.Name.NamespaceName;
256 | element.Name = XName.Get(element.Name.LocalName, nsName);
257 | }
258 | else {
259 | // remove element
260 | element.Remove();
261 |
262 | return false; // stop processing
263 | }
264 | }
265 |
266 | // process attributes
267 | foreach (var attribute in element.Attributes().ToArray()) {
268 | var attrMatch = Include(attribute.Name, removeNamespaceNames);
269 |
270 | if (attrMatch.HasValue) {
271 | updated = true;
272 |
273 | if (attrMatch.Value) {
274 | // replace attribute
275 | attribute.Remove();
276 |
277 | var attributeName = XName.Get(attribute.Name.LocalName);
278 |
279 | // make sure any existing attribute with this name is removed
280 | var sameNameAttribute = element.Attributes().FirstOrDefault(a => a.Name == attributeName);
281 | if (sameNameAttribute != null) {
282 | sameNameAttribute.Remove();
283 | }
284 |
285 | element.Add(new XAttribute(attributeName, attribute.Value));
286 | }
287 | else {
288 | // remove attribute
289 | attribute.Remove();
290 | }
291 | }
292 | }
293 |
294 | // add linenumber info
295 | element.SetAttributeValue(LineNumberName, ((IXmlLineInfo)element).LineNumber);
296 |
297 | return true;
298 | }
299 |
300 | private bool? Include(XName name, string[] removeNamespaceNames)
301 | {
302 | // first check if ignorable content should be removed
303 | if (this.removeIgnorableContent && removeNamespaceNames.Contains(name.NamespaceName)) {
304 | return false;
305 | }
306 |
307 | var condition = GetCondition(name);
308 |
309 | // no condition, ignore
310 | if (condition == null) {
311 | return null;
312 | }
313 |
314 | // try condition result cache
315 | bool result;
316 | if (!this.conditionResults.TryGetValue(condition, out result)) {
317 | if (condition.StartsWith("!")) {
318 | var conditionName = condition.Substring(1);
319 | result = !this.definedSymbols.Any(s => s == conditionName);
320 | }
321 | else {
322 | result = this.definedSymbols.Any(s => s == condition);
323 | }
324 |
325 | this.conditionResults[condition] = result;
326 | }
327 | return result;
328 | }
329 |
330 | private static string GetCondition(XName name)
331 | {
332 | if (IsCondition(name.NamespaceName)) {
333 | return name.NamespaceName.Substring(10);
334 | }
335 | return null;
336 | }
337 |
338 | private static bool IsCondition(string value)
339 | {
340 | return value.StartsWith("condition:", StringComparison.InvariantCulture);
341 | }
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/FirstFloor.Xcc/Xmlns.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace FirstFloor.Xcc
8 | {
9 | ///
10 | /// Defines XML namespaces.
11 | ///
12 | public static class Xmlns
13 | {
14 | ///
15 | /// The Markup Compatibility namespace.
16 | ///
17 | public const string MarkupCompatibility = "http://schemas.openxmlformats.org/markup-compatibility/2006";
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Microsoft Public License (Ms-PL)
2 |
3 | This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
4 |
5 | 1. Definitions
6 |
7 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
8 |
9 | A "contribution" is the original software, or any additions or changes to the software.
10 |
11 | A "contributor" is any person that distributes its contribution under this license.
12 |
13 | "Licensed patents" are a contributor's patent claims that read directly on its contribution.
14 |
15 | 2. Grant of Rights
16 |
17 | (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
18 |
19 | (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
20 |
21 | 3. Conditions and Limitations
22 |
23 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
24 |
25 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
26 |
27 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
28 |
29 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
30 |
31 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # XAML Conditional Compilation (XCC)
2 |
3 | Welcome to XCC, a preprocessor adding conditional compilation support to XAML files. Enable XCC in your project by installing a tiny NuGet package. Supporting *Windows Universal*, *WPF*, *Managed C++*, and *Xamarin Forms* projects. Other project types have not been tested.
4 |
5 | **Note**: XCC is a proof of concept. No guarantees whatsoever, use at your own risk.
6 |
7 | See the [wiki](https://github.com/firstfloorsoftware/xcc/wiki) for documentation
8 |
9 | [](https://ci.appveyor.com/project/kozw/xcc)
10 | [](http://nuget.org/packages/xcc)
11 |
--------------------------------------------------------------------------------