This change set contains the odata javascript library V3. The version
number is 1.1.2
diff --git a/BuildProcessTemplates/DefaultTemplate.11.1.xaml b/BuildProcessTemplates/DefaultTemplate.11.1.xaml
new file mode 100644
index 0000000..bf54edf
--- /dev/null
+++ b/BuildProcessTemplates/DefaultTemplate.11.1.xaml
@@ -0,0 +1,543 @@
+<Activity mc:Ignorable="sad" x:Class="TfsBuild.Process" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mt="clr-namespace:Microsoft.TeamFoundation;assembly=Microsoft.TeamFoundation.Common" xmlns:mtbc="clr-namespace:Microsoft.TeamFoundation.Build.Client;assembly=Microsoft.TeamFoundation.Build.Client" xmlns:mtbw="clr-namespace:Microsoft.TeamFoundation.Build.Workflow;assembly=Microsoft.TeamFoundation.Build.Workflow" xmlns:mtbwa="clr-namespace:Microsoft.TeamFoundation.Build.Workflow.Activities;assembly=Microsoft.TeamFoundation.Build.Workflow" xmlns:mtbwt="clr-namespace:Microsoft.TeamFoundation.Build.Workflow.Tracking;assembly=Microsoft.TeamFoundation.Build.Workflow" xmlns:mttbb="clr-namespace:Microsoft.TeamFoundation.TestImpact.BuildIntegration.BuildActivities;assembly=Microsoft.TeamFoundation.TestImpact.BuildIntegration" xmlns:mtvc="clr-namespace:Microsoft.TeamFoundation.VersionControl.Client;assembly=Microsoft.TeamFoundation.VersionControl.Client" xmlns:mtvco="clr-namespace:Microsoft.TeamFoundation.VersionControl.Common;assembly=Microsoft.TeamFoundation.VersionControl.Common" xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:sad="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:sad1="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sl="clr-namespace:System.Linq;assembly=System.Core" xmlns:this="clr-namespace:TfsBuild;" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <x:Members>
+    <x:Property Name="BuildSettings" Type="InArgument(mtbwa:BuildSettings)" />
+    <x:Property Name="TestSpecs" Type="InArgument(mtbwa:TestSpecList)" />
+    <x:Property Name="BuildNumberFormat" Type="InArgument(x:String)" />
+    <x:Property Name="SolutionSpecificBuildOutputs" Type="InArgument(x:Boolean)" />
+    <x:Property Name="CleanWorkspace" Type="InArgument(mtbwa:CleanWorkspaceOption)" />
+    <x:Property Name="RunCodeAnalysis" Type="InArgument(mtbwa:CodeAnalysisOption)" />
+    <x:Property Name="SourceAndSymbolServerSettings" Type="InArgument(mtbwa:SourceAndSymbolServerSettings)" />
+    <x:Property Name="AgentSettings" Type="InArgument(mtbwa:AgentSettings)" />
+    <x:Property Name="AssociateChangesetsAndWorkItems" Type="InArgument(x:Boolean)" />
+    <x:Property Name="CreateWorkItem" Type="InArgument(x:Boolean)" />
+    <x:Property Name="MSBuildArguments" Type="InArgument(x:String)" />
+    <x:Property Name="MSBuildPlatform" Type="InArgument(mtbwa:ToolPlatform)" />
+    <x:Property Name="MSBuildMultiProc" Type="InArgument(x:Boolean)" />
+    <x:Property Name="PerformTestImpactAnalysis" Type="InArgument(x:Boolean)" />
+    <x:Property Name="CreateLabel" Type="InArgument(x:Boolean)" />
+    <x:Property Name="DisableTests" Type="InArgument(x:Boolean)" />
+    <x:Property Name="GetVersion" Type="InArgument(x:String)" />
+    <x:Property Name="PrivateDropLocation" Type="InArgument(x:String)" />
+    <x:Property Name="Verbosity" Type="InArgument(mtbw:BuildVerbosity)" />
+    <x:Property Name="Metadata" Type="mtbw:ProcessParameterMetadataCollection" />
+    <x:Property Name="SupportedReasons" Type="mtbc:BuildReason" />
+    <x:Property Name="BuildProcessVersion" Type="x:String" />
+  </x:Members>
+  <this:Process.BuildSettings>[New Microsoft.TeamFoundation.Build.Workflow.Activities.BuildSettings()]</this:Process.BuildSettings>
+  <this:Process.DisableTests>[False]</this:Process.DisableTests>
+  <this:Process.TestSpecs>[New Microsoft.TeamFoundation.Build.Workflow.Activities.TestSpecList(New Microsoft.TeamFoundation.Build.Workflow.Activities.AgileTestPlatformSpec("**\*test*.dll"))]</this:Process.TestSpecs>
+  <this:Process.BuildNumberFormat>["$(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.r)"]</this:Process.BuildNumberFormat>
+  <this:Process.SolutionSpecificBuildOutputs>[False]</this:Process.SolutionSpecificBuildOutputs>
+  <this:Process.AssociateChangesetsAndWorkItems>[True]</this:Process.AssociateChangesetsAndWorkItems>
+  <this:Process.CreateWorkItem>[True]</this:Process.CreateWorkItem>
+  <this:Process.CleanWorkspace>[Microsoft.TeamFoundation.Build.Workflow.Activities.CleanWorkspaceOption.All]</this:Process.CleanWorkspace>
+  <this:Process.MSBuildArguments>
+    <InArgument x:TypeArguments="x:String" />
+  </this:Process.MSBuildArguments>
+  <this:Process.RunCodeAnalysis>[Microsoft.TeamFoundation.Build.Workflow.Activities.CodeAnalysisOption.AsConfigured]</this:Process.RunCodeAnalysis>
+  <this:Process.MSBuildMultiProc>[True]</this:Process.MSBuildMultiProc>
+  <this:Process.MSBuildPlatform>[Microsoft.TeamFoundation.Build.Workflow.Activities.ToolPlatform.Auto]</this:Process.MSBuildPlatform>
+  <this:Process.PerformTestImpactAnalysis>[True]</this:Process.PerformTestImpactAnalysis>
+  <this:Process.SourceAndSymbolServerSettings>[New Microsoft.TeamFoundation.Build.Workflow.Activities.SourceAndSymbolServerSettings(True, Nothing)]</this:Process.SourceAndSymbolServerSettings>
+  <this:Process.CreateLabel>[True]</this:Process.CreateLabel>
+  <this:Process.GetVersion>
+    <InArgument x:TypeArguments="x:String" />
+  </this:Process.GetVersion>
+  <this:Process.AgentSettings>[New Microsoft.TeamFoundation.Build.Workflow.Activities.AgentSettings() With {.MaxWaitTime = New System.TimeSpan(4, 0, 0), .MaxExecutionTime = New System.TimeSpan(0, 0, 0), .TagComparison = Microsoft.TeamFoundation.Build.Workflow.Activities.TagComparison.MatchExactly }]</this:Process.AgentSettings>
+  <this:Process.Verbosity>[Microsoft.TeamFoundation.Build.Workflow.BuildVerbosity.Normal]</this:Process.Verbosity>
+  <this:Process.Metadata>
+    <mtbw:ProcessParameterMetadataCollection>
+      <mtbw:ProcessParameterMetadata BrowsableWhen="EditingDefinition" Category="#300 Advanced" DisplayName="MSBuild Multi-Proc" Description="Enable MSBuid Multi-proc to build your solutions' projects in parallel, when possible, using all available processors on the build server." ParameterName="MSBuildMultiProc" />
+      <mtbw:ProcessParameterMetadata BrowsableWhen="EditingDefinition" Category="#300 Advanced" DisplayName="Solution Specific Build Outputs" Description="True will put build outputs into folders based on the solution name. False will put all build outputs into the same folder." ParameterName="SolutionSpecificBuildOutputs" />
+    </mtbw:ProcessParameterMetadataCollection>
+  </this:Process.Metadata>
+  <this:Process.SupportedReasons>All</this:Process.SupportedReasons>
+  <this:Process.BuildProcessVersion>11.0</this:Process.BuildProcessVersion>  
+  <mva:VisualBasic.Settings>Assembly references and imported namespaces serialized as XML namespaces</mva:VisualBasic.Settings>
+  <Sequence mtbwt:BuildTrackingParticipant.Importance="None">
+    <Sequence.Variables>
+      <Variable x:TypeArguments="mtbc:IBuildDetail" Name="BuildDetail" />
+      <Variable x:TypeArguments="x:String"  Name="DropLocation" />
+    </Sequence.Variables>
+    <mtbwa:GetBuildDetail DisplayName="Get the Build" Result="[BuildDetail]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+    <Sequence DisplayName="Update Drop Location" mtbwt:BuildTrackingParticipant.Importance="Low">
+      <mtbwa:InvokeForReason DisplayName="Update Build Number for Triggered Builds" Reason="Triggered">
+        <mtbwa:UpdateBuildNumber BuildNumberFormat="[BuildNumberFormat]" DisplayName="Update Build Number" />
+      </mtbwa:InvokeForReason>
+      <If Condition="[(Not String.IsNullOrEmpty(BuildDetail.DropLocationRoot)) AndAlso (BuildDetail.Reason And Microsoft.TeamFoundation.Build.Client.BuildReason.Triggered) = BuildDetail.Reason]" DisplayName="If Build Reason is Triggered" mtbwt:BuildTrackingParticipant.Importance="Low">
+        <If.Then>
+          <Sequence mtbwt:BuildTrackingParticipant.Importance="None">
+            <Assign x:TypeArguments="x:String" mtbwt:BuildTrackingParticipant.Importance="None" Value="[BuildDropProvider.CombinePaths(BuildDetail.DropLocationRoot, BuildDetail.BuildDefinition.Name, BuildDetail.BuildNumber)]" To="[DropLocation]" />
+            <mtbwa:SetBuildProperties DisplayName="Set Drop Location" DropLocation="[DropLocation]" PropertiesToSet="DropLocation" mtbwt:BuildTrackingParticipant.Importance="Low" />
+          </Sequence>
+        </If.Then>
+      </If>
+      <If Condition="[(Not String.IsNullOrEmpty(PrivateDropLocation)) AndAlso BuildDetail.Reason = Microsoft.TeamFoundation.Build.Client.BuildReason.ValidateShelveset]" DisplayName="If Build Reason is ValidateShelveset" mtbwt:BuildTrackingParticipant.Importance="Low">
+        <If.Then>
+          <Sequence mtbwt:BuildTrackingParticipant.Importance="None">
+            <Assign x:TypeArguments="x:String" Value="[BuildDropProvider.CombinePaths(PrivateDropLocation, BuildDetail.BuildDefinition.Name, BuildDetail.BuildNumber)]" To="[DropLocation]" mtbwt:BuildTrackingParticipant.Importance="None" />
+            <mtbwa:SetBuildProperties DisplayName="Set Drop Location for Private Build" DropLocation="[DropLocation]" PropertiesToSet="DropLocation" mtbwt:BuildTrackingParticipant.Importance="Low" />
+          </Sequence>
+        </If.Then>
+      </If>
+    </Sequence>
+    <mtbwa:AgentScope DisplayName="Run On Agent" MaxExecutionTime="[AgentSettings.MaxExecutionTime]" MaxWaitTime="[AgentSettings.MaxWaitTime]" ReservationSpec="[AgentSettings.GetAgentReservationSpec()]">
+      <mtbwa:AgentScope.Variables>
+        <Variable x:TypeArguments="mtbc:IBuildAgent" Name="BuildAgent" />
+        <Variable x:TypeArguments="mtvc:Workspace" Name="Workspace" />
+        <Variable x:TypeArguments="x:String" Name="BuildDirectory" />
+        <Variable x:TypeArguments="x:String" Default="[BuildDetail.BuildNumber]" Name="LabelName" />
+        <Variable x:TypeArguments="x:String" Name="WorkspaceName" />
+        <Variable x:TypeArguments="x:String" Name="SourcesDirectory" />
+        <Variable x:TypeArguments="x:String" Name="BinariesDirectory" />
+        <Variable x:TypeArguments="x:String" Name="TestResultsDirectory" />
+      </mtbwa:AgentScope.Variables>
+      <Sequence DisplayName="Initialize Variables" mtbwt:BuildTrackingParticipant.Importance="Low">
+        <mtbwa:GetBuildAgent DisplayName="Get the Agent" Result="[BuildAgent]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+        <mtbwa:GetBuildDirectory DisplayName="Get the Build Directory" Result="[BuildDirectory]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+        <Assign x:TypeArguments="x:String" DisplayName="Initialize Workspace Name" To="[WorkspaceName]" Value="[String.Format(&quot;{0}_{1}_{2}&quot;, BuildDetail.BuildDefinition.Id, Microsoft.TeamFoundation.LinkingUtilities.DecodeUri(BuildAgent.Uri.AbsoluteUri).ToolSpecificId, BuildAgent.ServiceHost.Name)]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+        <Assign x:TypeArguments="x:String" DisplayName="Initialize Sources Directory" To="[SourcesDirectory]" Value="[String.Format(&quot;{0}\Sources&quot;, BuildDirectory)]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+        <Assign x:TypeArguments="x:String" DisplayName="Initialize Binaries Directory" To="[BinariesDirectory]" Value="[String.Format(&quot;{0}\Binaries&quot;, BuildDirectory)]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+        <Assign x:TypeArguments="x:String" DisplayName="Initialize TestResults Directory" To="[TestResultsDirectory]" Value="[String.Format(&quot;{0}\TestResults&quot;, BuildDirectory)]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+        <If Condition="[Not BuildSettings.HasPlatformConfigurations]" DisplayName="If Not BuildSettings.HasPlatformConfigurations" mtbwt:BuildTrackingParticipant.Importance="Low">
+          <If.Then>
+            <AddToCollection x:TypeArguments="mtbwa:PlatformConfiguration" DisplayName="Use Default Platform Configuration" Collection="[BuildSettings.PlatformConfigurations]" Item="[Microsoft.TeamFoundation.Build.Workflow.Activities.PlatformConfiguration.Default]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+          </If.Then>
+        </If>
+        <If Condition="[WorkspaceName.Length &gt; Microsoft.TeamFoundation.VersionControl.Common.RepositoryConstants.MaxWorkspaceNameSize]" DisplayName="If WorkspaceName &gt; MaxSize" mtbwt:BuildTrackingParticipant.Importance="Low">
+          <If.Then>
+            <Sequence mtbwt:BuildTrackingParticipant.Importance="None">
+              <mtbwa:WriteBuildWarning DisplayName="Write Workspace Size Warning" Message="[String.Format(&quot;The workspace name '{0}' exceeds the maximum allowed limit of '{1}' characters. Truncating it to match the maximum limit.&quot;, WorkspaceName, Microsoft.TeamFoundation.VersionControl.Common.RepositoryConstants.MaxWorkspaceNameSize)]" />
+              <Assign x:TypeArguments="x:String" DisplayName="Truncate WorkspaceName to MaxSize" To="[WorkspaceName]" Value="[WorkspaceName.Substring(0, Microsoft.TeamFoundation.VersionControl.Common.RepositoryConstants.MaxWorkspaceNameSize).TrimEnd()]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+            </Sequence>
+          </If.Then>
+        </If>
+      </Sequence>
+      <Sequence DisplayName="Initialize Workspace" mtbwt:BuildTrackingParticipant.Importance="Low">
+        <mtbwa:DeleteDirectory Directory="[TestResultsDirectory]" DisplayName="Delete Test Results Directory" Recursive="[True]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+        <If Condition="[Not CleanWorkspace = Microsoft.TeamFoundation.Build.Workflow.Activities.CleanWorkspaceOption.None]" DisplayName="If Not CleanWorkspace = CleanWorkspaceOption.None" mtbwt:BuildTrackingParticipant.Importance="Low">
+          <If.Then>
+            <mtbwa:DeleteDirectory Directory="[BinariesDirectory]" DisplayName="Delete Binaries Directory" mtbwt:BuildTrackingParticipant.Importance="Low" />
+          </If.Then>
+        </If>
+        <If Condition="[CleanWorkspace = Microsoft.TeamFoundation.Build.Workflow.Activities.CleanWorkspaceOption.All]" DisplayName="If CleanWorkspace = CleanWorkspaceOption.All" mtbwt:BuildTrackingParticipant.Importance="Low">
+          <If.Then>
+            <Sequence DisplayName="Delete Workspace and Sources Directory" mtbwt:BuildTrackingParticipant.Importance="Low">
+              <mtbwa:DeleteWorkspace DeleteLocalItems="[True]" DisplayName="Delete Workspace" Name="[WorkspaceName]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+              <mtbwa:DeleteDirectory Directory="[SourcesDirectory]" DisplayName="Delete Sources Directory" mtbwt:BuildTrackingParticipant.Importance="Low" />
+            </Sequence>
+          </If.Then>
+        </If>
+        <mtbwa:CreateWorkspace BuildDirectory="[BuildDirectory]" Comment="[&quot;Workspace Created by Team Build&quot;]" DisplayName="Create Workspace" Name="[WorkspaceName]" Result="[Workspace]" SourcesDirectory="[SourcesDirectory]" />
+        <If Condition="[CleanWorkspace = Microsoft.TeamFoundation.Build.Workflow.Activities.CleanWorkspaceOption.Outputs]" DisplayName="If CleanWorkspace = CleanWorkspaceOption.Outputs" mtbwt:BuildTrackingParticipant.Importance="Low">
+          <If.Then>
+            <ForEach x:TypeArguments="mtbwa:PlatformConfiguration" DisplayName="For Each Configuration in BuildSettings.PlatformConfigurations" Values="[BuildSettings.PlatformConfigurations]" mtbwt:BuildTrackingParticipant.Importance="Low">
+              <ActivityAction x:TypeArguments="mtbwa:PlatformConfiguration">
+                <ActivityAction.Argument>
+                  <DelegateInArgument x:TypeArguments="mtbwa:PlatformConfiguration" Name="platformConfiguration" />
+                </ActivityAction.Argument>
+                <Sequence DisplayName="Clean Configuration">
+                  <If Condition="[BuildSettings.HasProjectsToBuild]" DisplayName="If BuildSettings.HasProjectsToBuild" mtbwt:BuildTrackingParticipant.Importance="Low">
+                    <If.Then>
+                      <ForEach x:TypeArguments="x:String" DisplayName="For Each Project in BuildSettings.ProjectsToBuild" Values="[BuildSettings.ProjectsToBuild]" mtbwt:BuildTrackingParticipant.Importance="Low">
+                        <ActivityAction x:TypeArguments="x:String">
+                          <ActivityAction.Argument>
+                            <DelegateInArgument x:TypeArguments="x:String" Name="serverBuildProjectItem" />
+                          </ActivityAction.Argument>
+                          <Sequence DisplayName="Clean Project" mtbwt:BuildTrackingParticipant.Importance="Low">
+                            <Sequence.Variables>
+                              <Variable x:TypeArguments="x:String" Name="localBuildProjectItem" />
+                            </Sequence.Variables>
+                            <mtbwa:ConvertWorkspaceItem DisplayName="Convert Server Paths to Local Paths" Input="[serverBuildProjectItem]" Result="[localBuildProjectItem]" Workspace="[Workspace]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                            <If Condition="[System.IO.File.Exists(localBuildProjectItem)]" DisplayName="If File.Exists(Project)" mtbwt:BuildTrackingParticipant.Importance="Low">
+                              <If.Then>
+                                <mtbwa:MSBuild CommandLineArguments="[String.Format(&quot;/p:SkipInvalidConfigurations=true {0}&quot;, MSBuildArguments)]" Configuration="[platformConfiguration.Configuration]" DisplayName="Run MSBuild for Project" GenerateVSPropsFile="[True]" MaxProcesses="[If (MSBuildMultiProc, 0, 1)]" OutDir="[BinariesDirectory]" Platform="[platformConfiguration.Platform]" Project="[localBuildProjectItem]" Targets="[New String() { &quot;Clean&quot; }]" TargetsNotLogged="[New String() {&quot;GetNativeManifest&quot;, &quot;GetCopyToOutputDirectoryItems&quot;, &quot;GetTargetPath&quot;}]" ToolPlatform="[MSBuildPlatform]" Verbosity="[Verbosity]" />
+                              </If.Then>
+                            </If>
+                          </Sequence>
+                        </ActivityAction>
+                      </ForEach>
+                    </If.Then>
+                  </If>
+                </Sequence>
+              </ActivityAction>
+            </ForEach>
+          </If.Then>
+        </If>
+        <mtbwa:SyncWorkspace DisplayName="Get Workspace" VersionOverride="[GetVersion]" Workspace="[Workspace]">
+          <mtbwa:SyncWorkspace.RequestsFailed>
+            <ActivityAction x:TypeArguments="scg:ICollection(mtbc:IQueuedBuild)">
+              <ActivityAction.Argument>
+                <DelegateInArgument x:TypeArguments="scg:ICollection(mtbc:IQueuedBuild)" Name="failedRequests" />
+              </ActivityAction.Argument>
+              <mtbwa:RetryRequests Behavior="[Microsoft.TeamFoundation.Build.Workflow.Activities.RetryBehavior.DoNotBatch]" DisplayName="Mark Requests for Retry" Requests="[failedRequests]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+            </ActivityAction>
+          </mtbwa:SyncWorkspace.RequestsFailed>
+        </mtbwa:SyncWorkspace>
+      </Sequence>
+      <If Condition="[CreateLabel]" DisplayName="If CreateLabel" mtbwt:BuildTrackingParticipant.Importance="Low">
+        <If.Then>
+          <mtbwa:InvokeForReason DisplayName="Create and Set Label for non-Shelveset Builds" Reason="Manual, IndividualCI, BatchedCI, Schedule, ScheduleForced, UserCreated">
+            <mtbwa:LabelWorkspace Comment="[&quot;Label Created by Team Build&quot;]" DisplayName="Create Label" Name="[LabelName]" Scope="[String.Format(&quot;$/{0}&quot;, BuildDetail.BuildDefinition.TeamProject)]" Workspace="[Workspace]" />
+            <mtbwa:SetBuildProperties DisplayName="Set Label on BuildDetail" LabelName="[String.Format(&quot;{0}@$/{1}&quot;, LabelName, BuildDetail.BuildDefinition.TeamProject)]" PropertiesToSet="LabelName" mtbwt:BuildTrackingParticipant.Importance="Low" />
+          </mtbwa:InvokeForReason>
+        </If.Then>
+        <If.Else>
+          <mtbwa:WriteBuildMessage DisplayName="Write Message" Message="Not Labeling sources" Importance="[Microsoft.TeamFoundation.Build.Client.BuildMessageImportance.High]" />
+        </If.Else>
+      </If>
+      <TryCatch DisplayName="Try Compile, Test, and Associate Changesets and Work Items" mtbwt:BuildTrackingParticipant.Importance="Low">
+        <TryCatch.Finally>
+          <Sequence DisplayName="Revert Workspace and Copy Files to Drop Location" mtbwt:BuildTrackingParticipant.Importance="Low">
+            <mtbwa:InvokeForReason DisplayName="Revert Workspace for Shelveset Builds" Reason="CheckInShelveset, ValidateShelveset">
+              <mtbwa:RevertWorkspace DisplayName="Revert Workspace" Workspace="[Workspace]" />
+            </mtbwa:InvokeForReason>
+            <If  Condition="[Not String.IsNullOrEmpty(DropLocation)]" DisplayName="If DropLocation is Set" mtbwt:BuildTrackingParticipant.Importance="Low">
+              <If.Then>
+                <mtbwa:CopyDirectory DisplayName="Drop Files to Drop Location" Source="[BinariesDirectory]" Destination="[DropLocation]" />
+              </If.Then>
+            </If>
+          </Sequence>
+        </TryCatch.Finally>
+        <TryCatch.Try>
+          <Sequence mtbwt:BuildTrackingParticipant.Importance="None">
+            <Sequence.Variables>
+              <Variable x:TypeArguments="s:Exception" Name="compilationException" />
+              <Variable x:TypeArguments="scg:IList(mtvc:Changeset)" Name="associatedChangesets" />
+              <Variable x:TypeArguments="s:Boolean" Name="treatTestFailureAsBuildFailure" />
+            </Sequence.Variables>
+            <Parallel DisplayName="Compile, Test, and Associate Changesets and Work Items">
+              <TryCatch DisplayName="Try Compile and Test" mtbwt:BuildTrackingParticipant.Importance="Low">
+                <TryCatch.Try>
+                  <Sequence DisplayName="Compile and Test">
+                    <ForEach x:TypeArguments="mtbwa:PlatformConfiguration" DisplayName="For Each Configuration in BuildSettings.PlatformConfigurations" Values="[BuildSettings.PlatformConfigurations]" mtbwt:BuildTrackingParticipant.Importance="Low">
+                      <ActivityAction x:TypeArguments="mtbwa:PlatformConfiguration">
+                        <ActivityAction.Argument>
+                          <DelegateInArgument x:TypeArguments="mtbwa:PlatformConfiguration" Name="platformConfiguration" />
+                        </ActivityAction.Argument>
+                        <Sequence DisplayName="Compile and Test for Configuration" mtbwt:BuildTrackingParticipant.Importance="Low">
+                          <Sequence.Variables>
+                            <Variable x:TypeArguments="x:String" Name="outputDirectory" />
+                            <Variable x:TypeArguments="x:String" Name="logFileDropLocation" />
+                          </Sequence.Variables>
+                          <Sequence DisplayName="Initialize Variables" mtbwt:BuildTrackingParticipant.Importance="Low">
+                            <Assign x:TypeArguments="x:String" DisplayName="Create OutputDirectory Per Platform and Configuration" To="[outputDirectory]" Value="[If (platformConfiguration.IsEmpty Or BuildSettings.PlatformConfigurations.Count = 1, BinariesDirectory, If (platformConfiguration.IsPlatformEmptyOrAnyCpu, BinariesDirectory + &quot;\&quot; + platformConfiguration.Configuration, BinariesDirectory + &quot;\&quot; + platformConfiguration.Platform + &quot;\&quot; + platformConfiguration.Configuration))]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                            <If Condition="[Not String.IsNullOrEmpty(DropLocation)]" DisplayName="If DropLocation is Set" mtbwt:BuildTrackingParticipant.Importance="Low">
+                              <If.Then>
+                                <Assign x:TypeArguments="x:String" DisplayName="Initialize LogFile Drop Location" To="[logFileDropLocation]" Value="[If (platformConfiguration.IsEmpty Or BuildSettings.PlatformConfigurations.Count = 1, BuildDropProvider.CombinePaths(DropLocation, &quot;logs&quot;), If (platformConfiguration.IsPlatformEmptyOrAnyCpu, BuildDropProvider.CombinePaths(DropLocation, &quot;logs&quot;, platformConfiguration.Configuration), BuildDropProvider.CombinePaths(DropLocation, &quot;logs&quot;, platformConfiguration.Platform, platformConfiguration.Configuration)))]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                              </If.Then>
+                            </If>
+                          </Sequence>
+                          <If Condition="[BuildSettings.HasProjectsToBuild]" DisplayName="If BuildSettings.HasProjectsToBuild" mtbwt:BuildTrackingParticipant.Importance="Low">
+                            <If.Then>
+                              <ForEach x:TypeArguments="x:String" DisplayName="For Each Project in BuildSettings.ProjectsToBuild" Values="[BuildSettings.ProjectsToBuild]" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                <ActivityAction x:TypeArguments="x:String">
+                                  <ActivityAction.Argument>
+                                    <DelegateInArgument x:TypeArguments="x:String" Name="serverBuildProjectItem" />
+                                  </ActivityAction.Argument>
+                                  <TryCatch DisplayName="Try to Compile the Project" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                    <TryCatch.Try>
+                                      <Sequence DisplayName="Compile the Project" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                        <Sequence.Variables>
+                                          <Variable x:TypeArguments="x:String" Name="localProject" />
+                                          <Variable x:TypeArguments="x:String" Name="outputDirectoryPerProject" Default="[outputDirectory]" />
+                                        </Sequence.Variables>
+                                        <mtbwa:ConvertWorkspaceItem DisplayName="Convert Server Path to Local Path" Input="[serverBuildProjectItem]" Result="[localProject]" Workspace="[Workspace]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                        <If Condition="[SolutionSpecificBuildOutputs]" DisplayName="If Build Outputs are Solution-Specific" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                          <If.Then>
+                                            <Sequence DisplayName="Update Output Directory" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                              <Assign x:TypeArguments="x:String" DisplayName="Set Solution-Specific Output Directory" To="[outputDirectoryPerProject]" Value="[System.IO.Path.Combine(outputDirectory, System.IO.Path.GetFileNameWithoutExtension(localProject))]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                              <If DisplayName="If Output Directory Exists" Condition="[System.IO.Directory.Exists(outputDirectoryPerProject)]" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                                <If.Then>
+                                                  <mtbwa:WriteBuildWarning DisplayName="Write Duplicate Project Names Warning" Message="[String.Format(&quot;{0} conflicts with another solution/project. Build outputs for solutions/projects with the same name will be copied to the same directory. To separate the build outputs, change the name of one of the solutions/projects.&quot;, System.IO.Path.GetFileNameWithoutExtension(localProject))]" />
+                                                </If.Then>
+                                              </If>
+                                            </Sequence>
+                                          </If.Then>
+                                        </If>
+                                        <mtbwa:MSBuild CommandLineArguments="[String.Format(&quot;/p:SkipInvalidConfigurations=true {0}&quot;, MSBuildArguments)]" Configuration="[platformConfiguration.Configuration]" DisplayName="Run MSBuild for Project" GenerateVSPropsFile="[True]" LogFileDropLocation="[logFileDropLocation]" MaxProcesses="[If (MSBuildMultiProc, 0, 1)]" OutDir="[outputDirectoryPerProject]" Platform="[platformConfiguration.Platform]" Project="[localProject]" RunCodeAnalysis="[RunCodeAnalysis]" TargetsNotLogged="[New String() {&quot;GetNativeManifest&quot;, &quot;GetCopyToOutputDirectoryItems&quot;, &quot;GetTargetPath&quot;}]" ToolPlatform="[MSBuildPlatform]" Verbosity="[Verbosity]" />
+                                      </Sequence>
+                                    </TryCatch.Try>
+                                    <TryCatch.Catches>
+                                      <Catch x:TypeArguments="s:Exception">
+                                        <ActivityAction x:TypeArguments="s:Exception">
+                                          <ActivityAction.Argument>
+                                            <DelegateInArgument x:TypeArguments="s:Exception" Name="ex" />
+                                          </ActivityAction.Argument>
+                                          <Sequence DisplayName="Handle Exception">
+                                            <Sequence.Variables>
+                                              <Variable x:TypeArguments="scg:ICollection(mtbc:IQueuedBuild)" Name="failedRequests" />
+                                            </Sequence.Variables>
+                                            <mtbwa:SetBuildProperties CompilationStatus="[Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Failed]" DisplayName="Set CompilationStatus to Failed" PropertiesToSet="CompilationStatus" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                            <If Condition="[CreateWorkItem]" DisplayName="If CreateWorkItem" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                              <If.Then>
+                                                <mtbwa:InvokeForReason DisplayName="Create Work Item for non-Shelveset Builds" Reason="Manual, IndividualCI, BatchedCI, Schedule, ScheduleForced, UserCreated">
+                                                  <mtbwa:OpenWorkItem AssignedTo="[BuildDetail.RequestedFor]" Comment="[&quot;This work item was created by TFS Build on a build failure.&quot;]" CustomFields="[New Dictionary(Of String, String) From { {&quot;System.Reason&quot;, &quot;Build Failure&quot;}, {&quot;Microsoft.VSTS.TCM.ReproSteps&quot;, &quot;Start the build using TFS Build&quot;}, {&quot;Priority&quot;, &quot;1&quot;}, {&quot;Severity&quot;, &quot;1 - Critical&quot;} }]" DisplayName="Create Work Item" Title="[String.Format(&quot;Build Failure in Build: {0}&quot;, BuildDetail.BuildNumber)]" Type="[&quot;Bug&quot;]" />
+                                                </mtbwa:InvokeForReason>
+                                              </If.Then>
+                                            </If>
+                                            <mtbwa:GetApprovedRequests DisplayName="Get Requests Approved for Check In" Result="[failedRequests]" mtbwt:BuildTrackingParticipant.Importance="None" />
+                                            <mtbwa:RetryRequests Behavior="[Microsoft.TeamFoundation.Build.Workflow.Activities.RetryBehavior.DoNotBatch]" DisplayName="Mark Requests for Retry" Requests="[failedRequests]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                            <Rethrow DisplayName="Rethrow the exception so the build will stop" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                          </Sequence>
+                                        </ActivityAction>
+                                      </Catch>
+                                    </TryCatch.Catches>
+                                  </TryCatch>
+                                </ActivityAction>
+                              </ForEach>
+                            </If.Then>
+                          </If>
+                          <If Condition="[Not DisableTests]" DisplayName="If Not DisableTests" mtbwt:BuildTrackingParticipant.Importance="Low">
+                            <If.Then>
+                              <Sequence DisplayName="Run Tests" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                <If Condition="[Not TestSpecs Is Nothing]" DisplayName="If Not TestSpecs Is Nothing" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                  <If.Then>
+                                    <ForEach x:TypeArguments="mtbwa:TestSpec" DisplayName="For Each TestSpec in TestSpecs" Values="[TestSpecs]" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                      <ActivityAction x:TypeArguments="mtbwa:TestSpec">
+                                        <ActivityAction.Argument>
+                                          <DelegateInArgument x:TypeArguments="mtbwa:TestSpec" Name="spec" />
+                                        </ActivityAction.Argument>
+                                        <TryCatch DisplayName="Try Run Tests" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                          <TryCatch.Try>
+                                            <If Condition="[TypeOf spec Is Microsoft.TeamFoundation.Build.Workflow.Activities.AgileTestPlatformSpec]" DisplayName="If spec Is AgileTestPlatformSpec" mtbwt:BuildTrackingParticipant.Importance="None">
+                                              <If.Then>
+                                                <Sequence DisplayName="Run Visual Studio Test Runner for Test Sources" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                                  <Sequence.Variables>
+                                                    <Variable x:TypeArguments="mtbwa:AgileTestPlatformSpec" Name="agileTestPlatformAssembly" />
+                                                    <Variable x:TypeArguments="scg:IEnumerable(x:String)" Name="agileTestPlatformAssemblies" />
+                                                  </Sequence.Variables>
+                                                  <Assign x:TypeArguments="mtbwa:AgileTestPlatformSpec" DisplayName="Assign spec to agileTestPlatformAssembly" To="[agileTestPlatformAssembly]" Value="[DirectCast(spec, Microsoft.TeamFoundation.Build.Workflow.Activities.AgileTestPlatformSpec)]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                                  <mtbwa:FindMatchingFiles DisplayName="Find Visual Studio Test Platform Test Assemblies" MatchPattern="[String.Format(&quot;{0}\{1}&quot;, outputDirectory, agileTestPlatformAssembly.AssemblyFileSpec)]" Result="[agileTestPlatformAssemblies]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                                  <If Condition="[agileTestPlatformAssemblies.Count() &gt; 0]" DisplayName="If Visual Studio Test Platform Test Assemblies Found" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                                    <If.Then>
+                                                      <If Condition="[agileTestPlatformAssembly.HasRunSettingsFile]" DisplayName="If agileTestPlatformAssembly.HasRunSettingsFile" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                                        <If.Then>
+                                                          <Sequence DisplayName="Find Run Settings File And Run Visual Studio Test Runner" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                                            <Sequence.Variables>
+                                                              <Variable x:TypeArguments="x:String" Name="localRunSettings" />
+                                                            </Sequence.Variables>
+                                                            <mtbwa:GenerateRunSettings DisplayName="Generate Run Settings File" RunSettingsForTestRun="[agileTestPlatformAssembly.RunSettingsForTestRun]" Result="[localRunSettings]" Workspace="[Workspace]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                                            <mtbwa:RunTests DisplayName="Run Visual Studio Test Runner for Test Sources" RunName="[agileTestPlatformAssembly.RunName]" Flavor="[platformConfiguration.Configuration]" Platform="[platformConfiguration.Platform]" TestSources="[agileTestPlatformAssemblies]" RunSettings="[localRunSettings]" TestCaseFilter="[agileTestPlatformAssembly.TestCaseFilter]" ExecutionPlatform="[agileTestPlatformAssembly.ExecutionPlatform]" />
+                                                          </Sequence>
+                                                        </If.Then>
+                                                        <If.Else>
+                                                          <mtbwa:RunTests DisplayName="Run Visual Studio Test Runner for Test Sources" RunName="[agileTestPlatformAssembly.RunName]" Flavor="[platformConfiguration.Configuration]" Platform="[platformConfiguration.Platform]" TestSources="[agileTestPlatformAssemblies]" TestCaseFilter="[agileTestPlatformAssembly.TestCaseFilter]" ExecutionPlatform="[agileTestPlatformAssembly.ExecutionPlatform]" />
+                                                        </If.Else>
+                                                      </If>
+                                                    </If.Then>
+                                                  </If>
+                                                </Sequence>
+                                              </If.Then>
+                                              <If.Else>
+                                                <If Condition="[TypeOf spec Is Microsoft.TeamFoundation.Build.Workflow.Activities.TestMetadataFileSpec]" DisplayName="If spec Is TestMetadataFileSpec" mtbwt:BuildTrackingParticipant.Importance="None">
+                                                  <If.Then>
+                                                    <Sequence DisplayName="Run MSTest for Metadata File">
+                                                      <Sequence.Variables>
+                                                        <Variable x:TypeArguments="mtbwa:TestMetadataFileSpec" Name="testMetadataFile" />
+                                                        <Variable x:TypeArguments="x:String" Name="localTestMetadata" />
+                                                      </Sequence.Variables>
+                                                      <Assign x:TypeArguments="mtbwa:TestMetadataFileSpec" DisplayName="Assign spec to testMetadataFile" To="[testMetadataFile]" Value="[DirectCast(spec, Microsoft.TeamFoundation.Build.Workflow.Activities.TestMetadataFileSpec)]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                                      <mtbwa:ConvertWorkspaceItem DisplayName="Convert Server Path to Local Path" Input="[testMetadataFile.MetadataFileName]" Result="[localTestMetadata]" Workspace="[Workspace]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                                      <mtbwa:MSTest RunTitle="[testMetadataFile.RunName]" Category="[testMetadataFile.CategoryFilter]" DisplayName="Run MSTest for Metadata File" Flavor="[platformConfiguration.Configuration]" MaxPriority="[testMetadataFile.MaximumPriority]" MinPriority="[testMetadataFile.MinimumPriority]" PathToResultsFilesRoot="[TestResultsDirectory]" Platform="[platformConfiguration.Platform]" SearchPathRoot="[outputDirectory]" TestLists="[testMetadataFile.TestLists]" TestMetadata="[localTestMetadata]" TestSettings="[String.Empty]" CommandLineArguments="[testMetadataFile.MSTestCommandLineArgs]" />
+                                                    </Sequence>
+                                                  </If.Then>
+                                                  <If.Else>
+                                                    <Sequence DisplayName="Run MSTest for Test Assemblies" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                                      <Sequence.Variables>
+                                                        <Variable x:TypeArguments="mtbwa:TestAssemblySpec" Name="testAssembly" />
+                                                        <Variable x:TypeArguments="scg:IEnumerable(x:String)" Name="testAssemblies" />
+                                                        <Variable x:TypeArguments="x:String" Default="[String.Empty]" Name="testFlavor" />
+                                                        <Variable x:TypeArguments="x:String" Default="[String.Empty]" Name="testPlatform" />
+                                                      </Sequence.Variables>
+                                                      <Assign x:TypeArguments="mtbwa:TestAssemblySpec" DisplayName="Assign spec to testAssembly" To="[testAssembly]" Value="[DirectCast(spec, Microsoft.TeamFoundation.Build.Workflow.Activities.TestAssemblySpec)]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                                      <mtbwa:FindMatchingFiles DisplayName="Find Test Assemblies" MatchPattern="[String.Format(&quot;{0}\{1}&quot;, outputDirectory, testAssembly.AssemblyFileSpec)]" Result="[testAssemblies]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                                      <If Condition="[testAssemblies.Count() &gt; 0]" DisplayName="If Test Assemblies Found" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                                        <If.Then>
+                                                          <If Condition="[testAssembly.HasTestSettingsFile]" DisplayName="If testAssembly.HasTestSettingsFile" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                                            <If.Then>
+                                                              <Sequence DisplayName="Find Test Settings File And Run MSTest" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                                                <Sequence.Variables>
+                                                                  <Variable x:TypeArguments="x:String" Name="localTestSettings" />
+                                                                </Sequence.Variables>
+                                                                <mtbwa:ConvertWorkspaceItem DisplayName="Convert Server Path to Local Path" Input="[testAssembly.TestSettingsFileName]" Result="[localTestSettings]" Workspace="[Workspace]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                                                <mtbwa:MSTest RunTitle="[testAssembly.RunName]" Category="[testAssembly.CategoryFilter]" DisplayName="Run MSTest for Test Assemblies" Flavor="[platformConfiguration.Configuration]" MaxPriority="[testAssembly.MaximumPriority]" MinPriority="[testAssembly.MinimumPriority]" PathToResultsFilesRoot="[TestResultsDirectory]" Platform="[platformConfiguration.Platform]" SearchPathRoot="[outputDirectory]" TestContainers="[testAssemblies]" TestSettings="[localTestSettings]" CommandLineArguments="[testAssembly.MSTestCommandLineArgs]" />
+                                                              </Sequence>
+                                                            </If.Then>
+                                                            <If.Else>
+                                                              <mtbwa:MSTest RunTitle="[testAssembly.RunName]" Category="[testAssembly.CategoryFilter]" DisplayName="Run MSTest for Test Assemblies" Flavor="[platformConfiguration.Configuration]" MaxPriority="[testAssembly.MaximumPriority]" MinPriority="[testAssembly.MinimumPriority]" PathToResultsFilesRoot="[TestResultsDirectory]" Platform="[platformConfiguration.Platform]" SearchPathRoot="[outputDirectory]" TestContainers="[testAssemblies]" CommandLineArguments="[testAssembly.MSTestCommandLineArgs]" />
+                                                            </If.Else>
+                                                          </If>
+                                                        </If.Then>
+                                                      </If>
+                                                    </Sequence>
+                                                  </If.Else>
+                                                </If>
+                                              </If.Else>
+                                            </If>
+                                          </TryCatch.Try>
+                                          <TryCatch.Catches>
+                                            <Catch x:TypeArguments="s:Exception">
+                                              <ActivityAction x:TypeArguments="s:Exception">
+                                                <ActivityAction.Argument>
+                                                  <DelegateInArgument x:TypeArguments="s:Exception" Name="testException" />
+                                                </ActivityAction.Argument>
+                                                <Sequence DisplayName="Handle Test Run Exception">
+                                                  <Sequence.Variables>
+                                                    <Variable x:TypeArguments="scg:ICollection(mtbc:IQueuedBuild)" Name="failedRequests" />
+                                                  </Sequence.Variables>
+                                                  <If Condition="[Not (TypeOf testException Is Microsoft.TeamFoundation.Build.Workflow.Activities.TestFailureException)]" DisplayName="If testException is NOT TestFailureException" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                                    <If.Then>
+                                                      <mtbwa:WriteBuildError DisplayName="Write Test Failure Message" Message="[testException.Message]" />
+                                                    </If.Then>
+                                                  </If>
+                                                  <mtbwa:SetBuildProperties DisplayName="Set TestStatus to Failed" PropertiesToSet="TestStatus" TestStatus="[Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Failed]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                                  <If Condition="[spec.FailBuildOnFailure]" DisplayName="If spec.FailBuildOnFailure" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                                    <If.Then>
+                                                      <Assign x:TypeArguments="s:Boolean" DisplayName="Set treatTestFailureAsBuildFailure to True" To="[treatTestFailureAsBuildFailure]" Value="[True]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                                    </If.Then>
+                                                  </If>
+                                                  <mtbwa:GetApprovedRequests DisplayName="Get Requests Approved for Check In" Result="[failedRequests]" mtbwt:BuildTrackingParticipant.Importance="None" />
+                                                  <mtbwa:RetryRequests Behavior="[Microsoft.TeamFoundation.Build.Workflow.Activities.RetryBehavior.DoNotBatch]" DisplayName="Mark Requests for Retry" Requests="[failedRequests]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                                                </Sequence>
+                                              </ActivityAction>
+                                            </Catch>
+                                          </TryCatch.Catches>
+                                        </TryCatch>
+                                      </ActivityAction>
+                                    </ForEach>
+                                  </If.Then>
+                                </If>
+                              </Sequence>
+                            </If.Then>
+                            <If.Else>
+                              <If Condition="[(Not TestSpecs Is Nothing) And (TestSpecs.Count > 0)]" DisplayName="If TestSpecs Is Not Nothing or Empty" mtbwt:BuildTrackingParticipant.Importance="Low">
+                                <If.Then>
+                                  <mtbwa:WriteBuildWarning DisplayName="Write Warning" Message="No automated tests will be run for this build because tests have been disabled for this build definition. To enable these tests, edit this build definition and set the Disable Tests process parameter to false." />
+                                </If.Then>
+                              </If>
+                            </If.Else>
+                          </If>
+                        </Sequence>
+                      </ActivityAction>
+                    </ForEach>
+                    <If Condition="[BuildDetail.CompilationStatus = Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Unknown]" DisplayName="If CompilationStatus = Unknown" mtbwt:BuildTrackingParticipant.Importance="Low">
+                      <If.Then>
+                        <mtbwa:SetBuildProperties CompilationStatus="[Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Succeeded]" DisplayName="Set CompilationStatus to Succeeded" PropertiesToSet="CompilationStatus" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                      </If.Then>
+                    </If>
+                    <If Condition="[BuildDetail.TestStatus = Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Unknown]" DisplayName="If TestStatus = Unknown" mtbwt:BuildTrackingParticipant.Importance="Low">
+                      <If.Then>
+                        <mtbwa:SetBuildProperties DisplayName="Set TestStatus to Succeeded" PropertiesToSet="TestStatus" TestStatus="[Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Succeeded]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                      </If.Then>
+                    </If>
+                    <If Condition="[treatTestFailureAsBuildFailure And (BuildDetail.TestStatus = Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Failed)]" DisplayName="If TreatTestFailureAsBuildFailure And (TestStatus = Failed)" mtbwt:BuildTrackingParticipant.Importance="Low">
+                      <If.Then>
+                        <mtbwa:SetBuildProperties DisplayName="Set Status to Failed" PropertiesToSet="Status" Status="[Microsoft.TeamFoundation.Build.Client.BuildStatus.Failed]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                      </If.Then>
+                    </If>
+                  </Sequence>
+                </TryCatch.Try>
+                <TryCatch.Catches>
+                  <Catch x:TypeArguments="s:Exception">
+                    <ActivityAction x:TypeArguments="s:Exception">
+                      <ActivityAction.Argument>
+                        <DelegateInArgument x:TypeArguments="s:Exception" Name="compilationExceptionArgument" />
+                      </ActivityAction.Argument>
+                      <Assign x:TypeArguments="s:Exception" DisplayName="Save the Compilation Exception" To="[compilationException]" Value="[compilationExceptionArgument]" mtbwt:BuildTrackingParticipant.Importance="None" />
+                    </ActivityAction>
+                  </Catch>
+                </TryCatch.Catches>
+              </TryCatch>
+              <If Condition="[AssociateChangesetsAndWorkItems]" DisplayName="If AssociateChangesetsAndWorkItems" mtbwt:BuildTrackingParticipant.Importance="Low">
+                <If.Then>
+                  <If Condition="[CreateLabel]" DisplayName="If CreateLabel and AssociateChangesetsAndWorkItems" mtbwt:BuildTrackingParticipant.Importance="Low">
+                    <If.Then>
+                      <mtbwa:InvokeForReason DisplayName="Associate Changesets and Work Items for non-Shelveset Builds" Reason="Manual, IndividualCI, BatchedCI, Schedule, ScheduleForced, UserCreated">
+                        <mtbwa:AssociateChangesetsAndWorkItems DisplayName="Associate Changesets and Work Items" Result="[associatedChangesets]" />
+                      </mtbwa:InvokeForReason>
+                    </If.Then>
+                    <If.Else>
+                      <mtbwa:WriteBuildWarning DisplayName="Write Associate Changesets and Work Items Warning" Message="Cannot Associate Changesets and Work Items because the Label Sources option is set to False." />
+                    </If.Else>
+                  </If>
+                </If.Then>
+              </If>
+            </Parallel>
+            <If Condition="[Not compilationException Is Nothing]" DisplayName="If a Compilation Exception Occurred" mtbwt:BuildTrackingParticipant.Importance="Low">
+              <If.Then>
+                <Throw DisplayName="Rethrow Compilation Exception" Exception="[compilationException]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+              </If.Then>
+            </If>
+            <Parallel DisplayName="Get Impacted Tests, Index Sources and Publish Symbols">
+              <If Condition="[PerformTestImpactAnalysis]" DisplayName="If PerformTestImpactAnalysis" mtbwt:BuildTrackingParticipant.Importance="Low">
+                <If.Then>
+                  <Sequence DisplayName="Get Impacted Tests" mtbwt:BuildTrackingParticipant.Importance="Low">
+                    <Sequence.Variables>
+                      <Variable x:TypeArguments="scg:IEnumerable(x:String)" Name="assemblies" />
+                    </Sequence.Variables>
+                    <mtbwa:FindMatchingFiles DisplayName="Find Build Outputs" MatchPattern="[String.Format(&quot;{0}\**\*.dll;{0}\**\*.exe&quot;, BinariesDirectory)]" Result="[assemblies]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                    <mttbb:GetImpactedTests Assemblies="[assemblies]" AssociatedChangesets="[associatedChangesets]" BinariesRoot="[BinariesDirectory]" Build="[BuildDetail]" CodeChanges="{x:Null}" DisplayName="Get Impacted Tests" ImpactedTests="{x:Null}" Workspace="[Workspace]" />
+                  </Sequence>
+                </If.Then>
+              </If>
+              <If Condition="[SourceAndSymbolServerSettings.IndexSources Or SourceAndSymbolServerSettings.HasSymbolStorePath]" DisplayName="If SourceAndSymbolServerSettings.IndexSources Or SourceAndSymbolServerSettings.HasSymbolStorePath" mtbwt:BuildTrackingParticipant.Importance="Low">
+                <If.Then>
+                  <mtbwa:InvokeForReason DisplayName="Index Sources and Publish Symbols for Triggered Builds" Reason="Triggered">
+                    <mtbwa:InvokeForReason.Variables>
+                      <Variable x:TypeArguments="scg:IEnumerable(x:String)" Name="symbolFiles" />
+                    </mtbwa:InvokeForReason.Variables>
+                    <mtbwa:FindMatchingFiles DisplayName="Find Symbol Files" MatchPattern="[String.Format(&quot;{0}\**\*.pdb&quot;, BinariesDirectory)]" Result="[symbolFiles]" mtbwt:BuildTrackingParticipant.Importance="Low" />
+                    <If Condition="[SourceAndSymbolServerSettings.IndexSources]" DisplayName="If SourceAndSymbolServerSettings.IndexSources" mtbwt:BuildTrackingParticipant.Importance="Low">
+                      <If.Then>
+                        <TryCatch DisplayName="Try Index Sources" mtbwt:BuildTrackingParticipant.Importance="Low">
+                          <TryCatch.Try>
+                            <mtbwa:IndexSources DisplayName="Index Sources" FileList="[symbolFiles]" />
+                          </TryCatch.Try>
+                          <TryCatch.Catches>
+                            <Catch x:TypeArguments="s:Exception">
+                              <ActivityAction x:TypeArguments="s:Exception">
+                                <ActivityAction.Argument>
+                                  <DelegateInArgument x:TypeArguments="s:Exception" Name="exception" />
+                                </ActivityAction.Argument>
+                                <mtbwa:WriteBuildError DisplayName="Write Indexing Sources Error" Message="[exception.Message]" />
+                              </ActivityAction>
+                            </Catch>
+                          </TryCatch.Catches>
+                        </TryCatch>
+                      </If.Then>
+                    </If>
+                    <If Condition="[SourceAndSymbolServerSettings.HasSymbolStorePath]" DisplayName="If SourceAndSymbolServerSettings.HasSymbolStorePath" mtbwt:BuildTrackingParticipant.Importance="Low">
+                      <If.Then>
+                        <TryCatch DisplayName="Try Publish Symbols" mtbwt:BuildTrackingParticipant.Importance="Low">
+                          <TryCatch.Try>
+                            <mtbwa:SharedResourceScope DisplayName="Synchronize Access to Symbol Store" MaxExecutionTime="[TimeSpan.Zero]" MaxWaitTime="[New TimeSpan(1, 0, 0)]" ResourceName="[SourceAndSymbolServerSettings.SymbolStorePath]" mtbwt:BuildTrackingParticipant.Importance="Low">
+                              <mtbwa:PublishSymbols DisplayName="Publish Symbols" FileList="[symbolFiles]" ProductName="[BuildDetail.BuildDefinition.Name]" StorePath="[SourceAndSymbolServerSettings.SymbolStorePath]" Version="[BuildDetail.BuildNumber]" />
+                            </mtbwa:SharedResourceScope>
+                          </TryCatch.Try>
+                          <TryCatch.Catches>
+                            <Catch x:TypeArguments="s:Exception">
+                              <ActivityAction x:TypeArguments="s:Exception">
+                                <ActivityAction.Argument>
+                                  <DelegateInArgument x:TypeArguments="s:Exception" Name="exception" />
+                                </ActivityAction.Argument>
+                                <mtbwa:WriteBuildError DisplayName="Write Publishing Symbols Error" Message="[exception.Message]" />
+                              </ActivityAction>
+                            </Catch>
+                          </TryCatch.Catches>
+                        </TryCatch>
+                      </If.Then>
+                    </If>
+                  </mtbwa:InvokeForReason>
+                </If.Then>
+              </If>
+            </Parallel>
+          </Sequence>
+        </TryCatch.Try>
+      </TryCatch>
+    </mtbwa:AgentScope>
+    <mtbwa:InvokeForReason DisplayName="Check In Gated Changes for CheckInShelveset Builds" Reason="CheckInShelveset">
+      <mtbwa:CheckInGatedChanges DisplayName="Check In Gated Changes" />
+    </mtbwa:InvokeForReason>
+  </Sequence>
+</Activity>
diff --git a/BuildProcessTemplates/LabDefaultTemplate.11.xaml b/BuildProcessTemplates/LabDefaultTemplate.11.xaml
new file mode 100644
index 0000000..9e1fb0b
--- /dev/null
+++ b/BuildProcessTemplates/LabDefaultTemplate.11.xaml
@@ -0,0 +1,208 @@
+<Activity mc:Ignorable="sads sap" x:Class="TfsBuild.Process" this:Process.LabWorkflowParameters="[New Microsoft.TeamFoundation.Lab.Workflow.Activities.LabWorkflowDetails()]" this:Process.Verbosity="[Microsoft.TeamFoundation.Build.Workflow.BuildVerbosity.Normal]" this:Process.BuildNumberFormat="[&quot;$(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.r)&quot;]" this:Process.SupportedReasons="Manual, BatchedCI, Schedule, ScheduleForced" this:Process.TimeoutForDeploymentScriptInMinutes="30" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mt="clr-namespace:Microsoft.TeamFoundation;assembly=Microsoft.TeamFoundation.Common" xmlns:mtbc="clr-namespace:Microsoft.TeamFoundation.Build.Client;assembly=Microsoft.TeamFoundation.Build.Client" xmlns:mtbc1="clr-namespace:Microsoft.TeamFoundation.Build.Common;assembly=Microsoft.TeamFoundation.Build.Common" xmlns:mtbp="clr-namespace:Microsoft.TeamFoundation.Build.ProcessComponents;assembly=Microsoft.TeamFoundation.Build.ProcessComponents" xmlns:mtbw="clr-namespace:Microsoft.TeamFoundation.Build.Workflow;assembly=Microsoft.TeamFoundation.Build.Workflow" xmlns:mtbwa="clr-namespace:Microsoft.TeamFoundation.Build.Workflow.Activities;assembly=Microsoft.TeamFoundation.Build.Workflow" xmlns:mtbws="clr-namespace:Microsoft.TeamFoundation.Build.Workflow.Services;assembly=Microsoft.TeamFoundation.Build.Workflow" xmlns:mtlc="clr-namespace:Microsoft.TeamFoundation.Lab.Client;assembly=Microsoft.TeamFoundation.Lab.Client" xmlns:mtltc="clr-namespace:Microsoft.TeamFoundation.Lab.TestIntegration.Client;assembly=Microsoft.TeamFoundation.Lab.TestIntegration.Client" xmlns:mtlwa="clr-namespace:Microsoft.TeamFoundation.Lab.Workflow.Activities;assembly=Microsoft.TeamFoundation.Lab.Workflow.Activities" xmlns:mtlwc="clr-namespace:Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client;assembly=Microsoft.TeamFoundation.Lab.WorkflowIntegration.Client" xmlns:mttbb="clr-namespace:Microsoft.TeamFoundation.TestImpact.BuildIntegration.BuildActivities;assembly=Microsoft.TeamFoundation.TestImpact.BuildIntegration" xmlns:mttc="clr-namespace:Microsoft.TeamFoundation.TestManagement.Client;assembly=Microsoft.TeamFoundation.TestManagement.Client" xmlns:mtvc="clr-namespace:Microsoft.TeamFoundation.VersionControl.Client;assembly=Microsoft.TeamFoundation.VersionControl.Client" xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:s1="clr-namespace:System;assembly=System" xmlns:s2="clr-namespace:System;assembly=System.Core" xmlns:s3="clr-namespace:System;assembly=System.ServiceModel" xmlns:s4="clr-namespace:System;assembly=System.ComponentModel.Composition" xmlns:sa="clr-namespace:System.Activities;assembly=System.Activities" xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:sads="http://schemas.microsoft.com/netfx/2010/xaml/activities/debugger" xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sl="clr-namespace:System.Linq;assembly=System.Core" xmlns:this="clr-namespace:TfsBuild" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <x:Members>
+    <x:Property Name="BuildProcessVersion" Type="x:String" />
+    <x:Property Name="Metadata" Type="mtbw:ProcessParameterMetadataCollection" />
+    <x:Property Name="LabWorkflowParameters" Type="InArgument(mtlwa:LabWorkflowDetails)" />
+    <x:Property Name="Verbosity" Type="InArgument(mtbw:BuildVerbosity)" />
+    <x:Property Name="BuildNumberFormat" Type="InArgument(x:String)" />
+    <x:Property Name="SupportedReasons" Type="mtbc:BuildReason" />
+    <x:Property Name="TimeoutForDeploymentScriptInMinutes" Type="InArgument(x:Int32)">
+      <x:Property.Attributes>
+        <RequiredArgumentAttribute />
+      </x:Property.Attributes>
+    </x:Property>
+  </x:Members>
+  <this:Process.BuildProcessVersion>11.0</this:Process.BuildProcessVersion>
+  <this:Process.Metadata>
+    <mtbw:ProcessParameterMetadataCollection>
+      <mtbw:ProcessParameterMetadata BrowsableWhen="Always" Category="Misc" DisplayName="Timeout For Each Deployment Script (in Minutes)" ParameterName="TimeoutForDeploymentScriptInMinutes" />
+    </mtbw:ProcessParameterMetadataCollection>
+  </this:Process.Metadata>
+  <sap:VirtualizedContainerService.HintSize>920,3702</sap:VirtualizedContainerService.HintSize>
+  <mva:VisualBasic.Settings>Assembly references and imported namespaces serialized as XML namespaces</mva:VisualBasic.Settings>
+  <Sequence DisplayName="Application Deployment Workflow" sad:XamlDebuggerXmlReader.FileName="D:\VSTLM\src\vset\LabManager\LabCustomActivities\Templates\LabDefaultTemplate.xaml" sap:VirtualizedContainerService.HintSize="880,3662" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces">
+    <Sequence.Variables>
+      <Variable x:TypeArguments="mtbc:IBuildDetail" Name="BuildDetail" />
+      <Variable x:TypeArguments="x:String" Name="LabEnvironmentUri" />
+      <Variable x:TypeArguments="x:String" Name="BuildLocation" />
+      <Variable x:TypeArguments="x:Int64" Name="SnapshotId" />
+      <Variable x:TypeArguments="x:Int32" Name="QueueBuildId" />
+      <Variable x:TypeArguments="mtbc:BuildStatus" Default="[Microsoft.TeamFoundation.Build.Client.BuildStatus.Succeeded]" Name="BuildStatus" />
+      <Variable x:TypeArguments="mtbc:IBuildDetail" Name="ChildBuildDetail" />
+      <Variable x:TypeArguments="mtbc:IBuildDetail" Name="SelectedBuildDetail" />
+      <Variable x:TypeArguments="x:String" Name="BuildNumber" />
+      <Variable x:TypeArguments="mtlc:LabEnvironment" Name="LabEnvironment" />
+    </Sequence.Variables>   
+    <mtbwa:UpdateBuildNumber BuildNumberFormat="[BuildNumberFormat]" DisplayName="Update Build Number" sap:VirtualizedContainerService.HintSize="858,22" />
+    <mtbwa:GetBuildDetail DisplayName="Get Build Details" sap:VirtualizedContainerService.HintSize="858,22" Result="[BuildDetail]" />
+    <If Condition="[LabWorkflowParameters.BuildDetails.IsTeamSystemBuild = True AndAlso LabWorkflowParameters.BuildDetails.QueueNewBuild = True]" DisplayName="If Build is needed" sap:VirtualizedContainerService.HintSize="858,416">
+      <If.Then>
+        <Sequence DisplayName="Do Build" sap:VirtualizedContainerService.HintSize="301,308">
+          <sap:WorkflowViewStateService.ViewState>
+            <scg:Dictionary x:TypeArguments="x:String, x:Object">
+              <x:Boolean x:Key="IsExpanded">True</x:Boolean>
+            </scg:Dictionary>
+          </sap:WorkflowViewStateService.ViewState>
+          <mtlwa:RunWorkflow BuildController="{x:Null}" LabEnvironmentUri="{x:Null}" BuildDefinition="[LabWorkflowParameters.BuildDetails.BuildDefinitionName]" DisplayName="Start Build Workflow" sap:VirtualizedContainerService.HintSize="242,22" ProjectName="[BuildDetail.TeamProject]" Result="[QueueBuildId]" />
+          <mtlwa:WaitForWorkflow AllowPartiallySucceededBuild="True" BuildDetails="[ChildBuildDetail]" DisplayName="Wait For Build To Complete" sap:VirtualizedContainerService.HintSize="242,22" LabWorkflowType="[Microsoft.TeamFoundation.Lab.Workflow.Activities.WorkflowType.Build]" MaxConsecutiveFailuresToIgnoreDuringWaitForCompletion="[3]" MaxWaitTime="[TimeSpan.Zero]" QueueBuildId="[QueueBuildId]" RefreshInterval="[System.TimeSpan.FromMinutes(1)]" Result="[BuildStatus]" ThrowOnError="True" />
+          <Assign DisplayName="Set Build Location" sap:VirtualizedContainerService.HintSize="242,60">
+            <Assign.To>
+              <OutArgument x:TypeArguments="x:Uri">[LabWorkflowParameters.BuildDetails.BuildUri]</OutArgument>
+            </Assign.To>
+            <Assign.Value>
+              <InArgument x:TypeArguments="x:Uri">[ChildBuildDetail.Uri]</InArgument>
+            </Assign.Value>
+          </Assign>
+        </Sequence>
+      </If.Then>
+    </If>
+    <mtlwa:WriteDeploymentInformation Url="{x:Null}" DeploymentInformationType="[Microsoft.TeamFoundation.Build.Common.DeploymentInformationTypes.Deploy]" DisplayName="Update Deployment Summary" sap:VirtualizedContainerService.HintSize="858,22" Message="[String.Format(&quot;Lab environment: {0}&quot;, LabWorkflowParameters.EnvironmentDetails.LabEnvironmentName)]" />
+    <mtlwa:GetBuildLocationAndBuildNumber BuildDetails="[LabWorkflowParameters.BuildDetails]" BuildNumber="[BuildNumber]" DisplayName="Get Build Location And Build Number" sap:VirtualizedContainerService.HintSize="858,22" Result="[BuildLocation]" SelectedBuild="[SelectedBuildDetail]" />
+    <If Condition="[LabWorkflowParameters.BuildDetails.IsTeamSystemBuild = True]" DisplayName="Compute build location needed" sap:VirtualizedContainerService.HintSize="858,208">
+      <If.Then>
+        <Assign DisplayName="Compute build path" sap:VirtualizedContainerService.HintSize="291,100">
+          <Assign.To>
+            <OutArgument x:TypeArguments="x:String">[BuildLocation]</OutArgument>
+          </Assign.To>
+          <Assign.Value>
+            <InArgument x:TypeArguments="x:String">[If(LabWorkflowParameters.BuildDetails.Configuration Is Nothing, BuildLocation, If(LabWorkflowParameters.BuildDetails.Configuration.IsEmpty Or (SelectedBuildDetail.Information.GetNodesByType(Microsoft.TeamFoundation.Build.Common.InformationTypes.ConfigurationSummary, True)).Count = 1, BuildLocation, If(LabWorkflowParameters.BuildDetails.Configuration.IsPlatformEmptyOrAnyCpu, BuildLocation + "\" + LabWorkflowParameters.BuildDetails.Configuration.Configuration, BuildLocation + "\" + LabWorkflowParameters.BuildDetails.Configuration.Platform + "\" + LabWorkflowParameters.BuildDetails.Configuration.Configuration)))]</InArgument>
+          </Assign.Value>
+        </Assign>
+      </If.Then>
+    </If>
+    <If Condition="[LabWorkflowParameters.EnvironmentDetails.Disposition = Microsoft.TeamFoundation.Lab.Client.LabEnvironmentDisposition.Stored]" DisplayName="If user selected stored environment" sap:VirtualizedContainerService.HintSize="858,208">
+      <If.Then>
+        <Throw DisplayName="Indicate error" Exception="[New System.Exception(&quot;You have selected an environment that is stored in the library. Select an environment that is deployed on a team project host group.&quot;)]" sap:VirtualizedContainerService.HintSize="269,100" />
+      </If.Then>
+    </If>
+    <Assign DisplayName="Get Lab Environment Uri" sap:VirtualizedContainerService.HintSize="858,22" >
+      <Assign.To>
+        <OutArgument x:TypeArguments="x:String">[LabEnvironmentUri]</OutArgument>
+      </Assign.To>
+      <Assign.Value>
+        <InArgument x:TypeArguments="x:String">[LabWorkflowParameters.EnvironmentDetails.LabEnvironmentUri.ToString()]</InArgument>
+      </Assign.Value>
+    </Assign>
+    <mtlwa:GetLabEnvironment DisplayName="Get Lab Environment" LabEnvironmentUri="[LabEnvironmentUri]" Result="[LabEnvironment]" />
+    <If Condition="[LabWorkflowParameters.EnvironmentDetails.RevertToSnapshot = True]" DisplayName=" If Restore Snapshot" sap:VirtualizedContainerService.HintSize="858,316">
+      <If.Then>
+        <Sequence DisplayName="Restore Snapshot" sap:VirtualizedContainerService.HintSize="231,208">
+          <mtlwa:GetLabEnvironmentSnapshotId DisplayName="Get Snapshot Details" sap:VirtualizedContainerService.HintSize="200,22" LabEnvironmentUri="[LabEnvironmentUri]" Result="[SnapshotId]" SnapshotName="[LabWorkflowParameters.EnvironmentDetails.SnapshotName]" />
+          <mtlwa:RestoreLabEnvironment DisplayName="Restore Lab Environment to Snapshot" sap:VirtualizedContainerService.HintSize="200,22" LabEnvironmentUri="[LabEnvironmentUri]" SnapshotId="[SnapshotId]" />
+        </Sequence>
+      </If.Then>
+      <If.Else>
+        <Sequence DisplayName="No Clean Snapshot" >
+          <If Condition="[Not String.Equals(Microsoft.TeamFoundation.Lab.Client.LabEnvironment.UnmanagedProvider, labEnvironment.LabProvider)]" DisplayName= "If Virtual Environment">
+            <If.Then>
+              <mtlwa:WriteDeploymentInformation Url="{x:Null}" DeploymentInformationType="[Microsoft.TeamFoundation.Build.Common.DeploymentInformationTypes.Deploy]" DisplayName="Clean snapshot not specified " sap:VirtualizedContainerService.HintSize="208,208" Message="Build definition did not specify a clean snapshot. It is a best practice to use clean snapshot when running the lab workflow." />
+            </If.Then>
+          </If>
+        </Sequence>        
+      </If.Else>
+    </If>
+    <If Condition="[LabWorkflowParameters.DeploymentDetails.DeploymentNeeded = True or LabWorkflowParameters.TestParameters.RunTest = True]" DisplayName="If deployment or test needed" sap:VirtualizedContainerService.HintSize="858,1214">
+      <If.Then>
+	<mtlwa:WaitForEnvironmentReady DisplayName="Wait For Environment To Be Ready" sap:VirtualizedContainerService.HintSize="711,22" LabEnvironmentUri="[LabEnvironmentUri]" MaxWaitTime="[System.TimeSpan.FromMinutes(10)]" />
+      </If.Then>
+    </If>
+    <If Condition="[LabWorkflowParameters.DeploymentDetails.DeploymentNeeded = True]" DisplayName="If deployment needed" sap:VirtualizedContainerService.HintSize="858,1214">
+      <If.Then>
+        <Sequence DisplayName="Do deployment" sap:VirtualizedContainerService.HintSize="733,1106">                    
+          <mtlwa:ReserveEnvironmentForDeployment DisplayName="Reserve Environment For Deployment" sap:VirtualizedContainerService.HintSize="711,22" LabEnvironmentUri="[LabEnvironmentUri]" />
+            <TryCatch DisplayName="Deploy Build on Environment">
+              <TryCatch.Try>
+                <Sequence DisplayName="Deploying Build">
+                  <ForEach x:TypeArguments="x:String" DisplayName="Run Deployment scripts" sap:VirtualizedContainerService.HintSize="711,254" Values="[LabWorkflowParameters.DeploymentDetails.Scripts]">
+                    <ActivityAction x:TypeArguments="x:String">
+                      <ActivityAction.Argument>
+                        <DelegateInArgument x:TypeArguments="x:String" Name="deploymentConfigurationPair" />
+                      </ActivityAction.Argument>
+                      <mtlwa:RunDeploymentTask BuildLocation="[BuildLocation]" DeploymentScriptDetails="[deploymentConfigurationPair]" DisplayName="Run Deployment Task" sap:VirtualizedContainerService.HintSize="200,22" LabEnvironmentUri="[LabEnvironmentUri]" MaxWaitTime="[TimeSpan.FromMinutes(TimeoutForDeploymentScriptInMinutes)]" ThrowOnError="True" UseRoleForDeployment="[LabWorkflowParameters.DeploymentDetails.UseRoleForDeployment]" />
+                    </ActivityAction>
+                  </ForEach>
+                </Sequence>
+              </TryCatch.Try>
+              <TryCatch.Finally>
+                <mtlwa:ReleaseEnvironmentFromDeployment DisplayName="Release Environment From Deployment" sap:VirtualizedContainerService.HintSize="711,22" LabEnvironmentUri="[LabEnvironmentUri]" />
+              </TryCatch.Finally>
+            </TryCatch>
+          <mtlwa:WriteDeploymentInformation DeploymentInformationType="[Microsoft.TeamFoundation.Build.Common.DeploymentInformationTypes.Deploy]" DisplayName="Application Deployment Succeeded" sap:VirtualizedContainerService.HintSize="711,22" Message="[String.Format(&quot;The application was deployed successfully from the following build location:&quot;)]" Url="[BuildLocation]" />
+          <If Condition="[LabWorkflowParameters.DeploymentDetails.TakePostDeploymentSnapshot = True]" DisplayName="Post Deployment Snapshot" sap:VirtualizedContainerService.HintSize="711,626">
+            <If.Then>
+              <Sequence DisplayName="Take Post deployment Snapshot " sap:VirtualizedContainerService.HintSize="486,518">
+                <Sequence.Variables>
+                  <Variable x:TypeArguments="x:Int64" Name="PostDeploymentSnapshotChainId" />
+                  <Variable x:TypeArguments="x:String" Default="[String.Format(&quot;{0}_{1}&quot;, BuildDetail.BuildDefinition.Name, BuildDetail.BuildNumber)]" Name="PostDeploymentSnapshotName" />
+                </Sequence.Variables>
+                <If Condition="[String.IsNullOrEmpty(LabWorkflowParameters.DeploymentDetails.PostDeploymentSnapshotName) = False]" DisplayName="Check snapshot name" sap:VirtualizedContainerService.HintSize="464,208">
+                  <If.Then>
+                    <Assign sap:VirtualizedContainerService.HintSize="291,100">
+                      <Assign.To>
+                        <OutArgument x:TypeArguments="x:String">[PostDeploymentSnapshotName]</OutArgument>
+                      </Assign.To>
+                      <Assign.Value>
+                        <InArgument x:TypeArguments="x:String">[If(LabWorkflowParameters.BuildDetails.IsTeamSystemBuild = True,String.Format("{0}_{1}_{2}", LabWorkflowParameters.DeploymentDetails.PostDeploymentSnapshotName, BuildNumber,BuildDetail.BuildNumber),String.Format("{0}_{1}", LabWorkflowParameters.DeploymentDetails.PostDeploymentSnapshotName, BuildDetail.BuildNumber))]</InArgument>
+                      </Assign.Value>
+                    </Assign>
+                  </If.Then>
+                </If>
+                <mtlwa:SnapshotLabEnvironment DisplayName="Taking Post Deployment snapshot" sap:VirtualizedContainerService.HintSize="464,22" LabEnvironmentUri="[LabEnvironmentUri]" SnapshotChainId="[PostDeploymentSnapshotChainId]" SnapshotName="[PostDeploymentSnapshotName]" />
+                <mtlwa:WriteDeploymentInformation Url="{x:Null}" DeploymentInformationType="[Microsoft.TeamFoundation.Build.Common.DeploymentInformationTypes.Deploy]" DisplayName="Taking Snapshot succeeded" sap:VirtualizedContainerService.HintSize="464,22" Message="[String.Format(&quot;The following snapshot was taken after the deployment was finished: {0}&quot;, PostDeploymentSnapshotName)]" />
+                <mtlwa:WriteDeploymentInformation DeploymentInformationType="[Microsoft.TeamFoundation.Build.Common.DeploymentInformationTypes.ConnectToSnapshot]" DisplayName="Added connection link to the Snapshot" sap:VirtualizedContainerService.HintSize="464,22" Message="[PostDeploymentSnapshotName]" Url="[PostDeploymentSnapshotChainId.ToString()]" />
+              </Sequence>
+            </If.Then>
+            <If.Else>
+              <Sequence DisplayName = "No Post Deployment Snapshot"> 
+                <If Condition="[Not String.Equals(Microsoft.TeamFoundation.Lab.Client.LabEnvironment.UnmanagedProvider, labEnvironment.LabProvider)]" DisplayName= "If Virtual Environment">
+                  <If.Then>
+                    <mtlwa:WriteDeploymentInformation Url="{x:Null}" DeploymentInformationType="[Microsoft.TeamFoundation.Build.Common.DeploymentInformationTypes.Deploy]" DisplayName="Post deployment snapshot not specified" sap:VirtualizedContainerService.HintSize="200,518" Message="Build definition did not specify a post deployment snapshot. It is a best practice to take post deployment snapshot when running the lab workflow." />
+                  </If.Then>
+                </If>
+              </Sequence>                
+            </If.Else>
+          </If>
+        </Sequence>
+      </If.Then>
+    </If>
+    <If Condition="[LabWorkflowParameters.TestParameters.RunTest = True]" DisplayName="Run Tests on Environment" sap:VirtualizedContainerService.HintSize="858,604">
+      <If.Then>
+        <Sequence DisplayName="Run Tests" sap:VirtualizedContainerService.HintSize="656,498">
+          <Sequence.Variables>
+            <Variable x:TypeArguments="mtltc:TestingCapabilityInformation" Name="TestCapabilityInfo" />
+            <Variable x:TypeArguments="x:String" Name="variable1" />
+            <Variable x:TypeArguments="mtlwa:TestRunStatistics" Default="[New Microsoft.TeamFoundation.Lab.Workflow.Activities.TestRunStatistics()]" Name="TestResults" />
+          </Sequence.Variables>
+          <mtlwa:ExecuteRemoteTestRun2 MaxWaitTime="{x:Null}" TestEnvironment="{x:Null}" BuildNumber="[BuildNumber]" BuildDefinitionName="[LabWorkflowParameters.BuildDetails.BuildDefinitionName]" DisplayName="Running Tests" sap:VirtualizedContainerService.HintSize="634,22" LabEnvironmentUri="[LabEnvironmentUri]" MaxConsecutiveFailuresToIgnoreDuringWaitForCompletion="[3]" RefreshInterval="[System.TimeSpan.FromMinutes(1)]" Result="[TestResults]" TestDirectory="[BuildLocation]" TestParameters="[LabWorkflowParameters.TestParameters]" Title="[String.Format(&quot;{0}&quot;, BuildDetail.BuildNumber)]" />
+          <If Condition="[TestResults.PassedTests &lt;&gt; TestResults.TotalTests Or TestResults.TestRunStatus &lt;&gt; TestManagement.Client.TestRunState.Completed]" DisplayName="If all tests have not passed" sap:VirtualizedContainerService.HintSize="634,312">
+            <If.Then>
+              <If Condition="[(LabWorkflowParameters.BuildDetails.IsTeamSystemBuild = True AndAlso LabWorkflowParameters.BuildDetails.QueueNewBuild = True) Or (LabWorkflowParameters.DeploymentDetails.DeploymentNeeded = True)]" DisplayName="Set build status" sap:VirtualizedContainerService.HintSize="509,206">
+                <If.Then>
+                  <Assign DisplayName="Partially succeeded" sap:VirtualizedContainerService.HintSize="242,100">
+                    <Assign.To>
+                      <OutArgument x:TypeArguments="mtbc:BuildStatus">[BuildStatus]</OutArgument>
+                    </Assign.To>
+                    <Assign.Value>
+                      <InArgument x:TypeArguments="mtbc:BuildStatus">[Microsoft.TeamFoundation.Build.Client.BuildStatus.PartiallySucceeded]</InArgument>
+                    </Assign.Value>
+                  </Assign>
+                </If.Then>
+                <If.Else>
+                  <Assign DisplayName="Failed" sap:VirtualizedContainerService.HintSize="242,100">
+                    <Assign.To>
+                      <OutArgument x:TypeArguments="mtbc:BuildStatus">[BuildStatus]</OutArgument>
+                    </Assign.To>
+                    <Assign.Value>
+                      <InArgument x:TypeArguments="mtbc:BuildStatus">[Microsoft.TeamFoundation.Build.Client.BuildStatus.Failed]</InArgument>
+                    </Assign.Value>
+                  </Assign>
+                </If.Else>
+              </If>
+            </If.Then>
+          </If>
+        </Sequence>
+      </If.Then>
+    </If>
+    <mtbwa:SetBuildProperties DisplayName="Set build status" sap:VirtualizedContainerService.HintSize="858,22" PropertiesToSet="Status" Status="[BuildStatus]" />
+  </Sequence>
+</Activity>
\ No newline at end of file
diff --git a/BuildProcessTemplates/UpgradeTemplate.xaml b/BuildProcessTemplates/UpgradeTemplate.xaml
new file mode 100644
index 0000000..b3ee07f
--- /dev/null
+++ b/BuildProcessTemplates/UpgradeTemplate.xaml
@@ -0,0 +1,76 @@
+<Activity mc:Ignorable="sad" x:Class="TfsBuild.Process" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mtbc="clr-namespace:Microsoft.TeamFoundation.Build.Client;assembly=Microsoft.TeamFoundation.Build.Client" xmlns:mtbw="clr-namespace:Microsoft.TeamFoundation.Build.Workflow;assembly=Microsoft.TeamFoundation.Build.Workflow" xmlns:mtbwa="clr-namespace:Microsoft.TeamFoundation.Build.Workflow.Activities;assembly=Microsoft.TeamFoundation.Build.Workflow" xmlns:mtbwt="clr-namespace:Microsoft.TeamFoundation.Build.Workflow.Tracking;assembly=Microsoft.TeamFoundation.Build.Workflow" xmlns:mtvc="clr-namespace:Microsoft.TeamFoundation.VersionControl.Client;assembly=Microsoft.TeamFoundation.VersionControl.Client" xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:sad="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:sad1="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:this="clr-namespace:TfsBuild;" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <x:Members>
+    <x:Property Name="ConfigurationFolderPath" Type="InArgument(x:String)" />
+    <x:Property Name="AgentSettings" Type="InArgument(mtbwa:AgentSettings)" />
+    <x:Property Name="MSBuildArguments" Type="InArgument(x:String)" />
+    <x:Property Name="MSBuildPlatform" Type="InArgument(mtbwa:ToolPlatform)" />
+    <x:Property Name="DoNotDownloadBuildType" Type="InArgument(x:Boolean)" />
+    <x:Property Name="LogFilePerProject" Type="InArgument(x:Boolean)" />
+    <x:Property Name="SourcesSubdirectory" Type="InArgument(x:String)" />
+    <x:Property Name="BinariesSubdirectory" Type="InArgument(x:String)" />
+    <x:Property Name="TestResultsSubdirectory" Type="InArgument(x:String)" />
+    <x:Property Name="RecursionType" Type="InArgument(mtvc:RecursionType)" />
+    <x:Property Name="Verbosity" Type="InArgument(mtbw:BuildVerbosity)" />
+    <x:Property Name="Metadata" Type="mtbw:ProcessParameterMetadataCollection" />
+    <x:Property Name="SupportedReasons" Type="mtbc:BuildReason" />
+  </x:Members>
+  <this:Process.ConfigurationFolderPath>
+    <InArgument x:TypeArguments="x:String" />
+  </this:Process.ConfigurationFolderPath>
+  <this:Process.AgentSettings>[New Microsoft.TeamFoundation.Build.Workflow.Activities.AgentSettings() With {.MaxWaitTime = New System.TimeSpan(4, 0, 0), .MaxExecutionTime = New System.TimeSpan(0, 0, 0), .TagComparison = Microsoft.TeamFoundation.Build.Workflow.Activities.TagComparison.MatchExactly }]</this:Process.AgentSettings>
+  <this:Process.MSBuildArguments>
+    <InArgument x:TypeArguments="x:String" />
+  </this:Process.MSBuildArguments>
+  <this:Process.MSBuildPlatform>[Microsoft.TeamFoundation.Build.Workflow.Activities.ToolPlatform.Auto]</this:Process.MSBuildPlatform>
+  <this:Process.DoNotDownloadBuildType>[False]</this:Process.DoNotDownloadBuildType>
+  <this:Process.LogFilePerProject>[False]</this:Process.LogFilePerProject>
+  <this:Process.SourcesSubdirectory>
+    <InArgument x:TypeArguments="x:String" />
+  </this:Process.SourcesSubdirectory>
+  <this:Process.BinariesSubdirectory>
+    <InArgument x:TypeArguments="x:String" />
+  </this:Process.BinariesSubdirectory>
+  <this:Process.TestResultsSubdirectory>
+    <InArgument x:TypeArguments="x:String" />
+  </this:Process.TestResultsSubdirectory>
+  <this:Process.RecursionType>[Microsoft.TeamFoundation.VersionControl.Client.RecursionType.OneLevel]</this:Process.RecursionType>
+  <this:Process.Verbosity>[Microsoft.TeamFoundation.Build.Workflow.BuildVerbosity.Normal]</this:Process.Verbosity>
+  <this:Process.Metadata>
+    <mtbw:ProcessParameterMetadataCollection />
+  </this:Process.Metadata>
+  <this:Process.SupportedReasons>All</this:Process.SupportedReasons>
+  <mva:VisualBasic.Settings>Assembly references and imported namespaces serialized as XML namespaces</mva:VisualBasic.Settings>
+  <Sequence mtbwt:BuildTrackingParticipant.Importance="None">
+    <Sequence.Variables>
+      <Variable x:TypeArguments="mtbc:IBuildDetail" Name="BuildDetail" />
+    </Sequence.Variables>
+    <mtbwa:GetBuildDetail DisplayName="Get the Build" Result="[BuildDetail]" />
+    <mtbwa:InvokeForReason DisplayName="Update Build Number for Triggered Builds" Reason="Triggered">
+      <mtbwa:UpdateBuildNumber BuildNumberFormat="[&quot;$(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.r)&quot;]" DisplayName="Update Build Number" />
+    </mtbwa:InvokeForReason>
+    <mtbwa:AgentScope DisplayName="Run On Agent" MaxExecutionTime="[AgentSettings.MaxExecutionTime]" MaxWaitTime="[AgentSettings.MaxWaitTime]" ReservationSpec="[AgentSettings.GetAgentReservationSpec()]">
+      <mtbwa:AgentScope.Variables>
+        <Variable x:TypeArguments="x:String" Name="buildDirectory" />
+      </mtbwa:AgentScope.Variables>
+      <mtbwa:GetBuildDirectory DisplayName="Get the Build Directory" Result="[buildDirectory]" />
+      <If Condition="[Not String.IsNullOrEmpty(ConfigurationFolderPath)]" DisplayName="If Not String.IsNullOrEmpty(ConfigurationFolderPath)">
+        <If.Then>
+          <mtbwa:TfsBuild BinariesSubdirectory="[BinariesSubdirectory]" BuildDirectory="[buildDirectory]" CommandLineArguments="[MSBuildArguments]" ConfigurationFolderPath="[ConfigurationFolderPath]" DisplayName="Run TfsBuild for Configuration Folder" DoNotDownloadBuildType="[DoNotDownloadBuildType]" LogFilePerProject="[LogFilePerProject]" RecursionType="[RecursionType]" SourcesSubdirectory="[SourcesSubdirectory]" TargetsNotLogged="[New String() {&quot;GetNativeManifest&quot;, &quot;GetCopyToOutputDirectoryItems&quot;, &quot;GetTargetPath&quot;}]" TestResultsSubdirectory="[TestResultsSubdirectory]" ToolPlatform="[MSBuildPlatform]" Verbosity="[Verbosity]" />
+        </If.Then>
+      </If>
+      <If Condition="[BuildDetail.CompilationStatus = Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Unknown]" DisplayName="If CompilationStatus = Unknown">
+        <If.Then>
+           <mtbwa:SetBuildProperties CompilationStatus="[Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Succeeded]" DisplayName="Set CompilationStatus to Succeeded" PropertiesToSet="CompilationStatus" />
+        </If.Then>
+      </If>
+      <If Condition="[BuildDetail.TestStatus = Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Unknown]" DisplayName="If TestStatus = Unknown">
+        <If.Then>
+          <mtbwa:SetBuildProperties DisplayName="Set TestStatus to Succeeded" PropertiesToSet="TestStatus" TestStatus="[Microsoft.TeamFoundation.Build.Client.BuildPhaseStatus.Succeeded]" />
+        </If.Then>
+      </If>
+    </mtbwa:AgentScope>
+    <mtbwa:InvokeForReason Reason="CheckInShelveset">
+      <mtbwa:CheckInGatedChanges DisplayName="Check In Gated Changes" />
+    </mtbwa:InvokeForReason>
+  </Sequence>
+</Activity>
\ No newline at end of file
diff --git a/BuildTools/ajaxmin/readme.txt b/BuildTools/ajaxmin/readme.txt
new file mode 100644
index 0000000..c74506c
--- /dev/null
+++ b/BuildTools/ajaxmin/readme.txt
@@ -0,0 +1,2 @@
+To enable the DataJS library to be minified, download the Ajax Minifier from
+http://ajaxmin.codeplex.com/ and copy the binaries into %DJSROOT%\BuildTools\ajaxmin
diff --git a/BuildTools/djsbuild.cmd b/BuildTools/djsbuild.cmd
new file mode 100644
index 0000000..0e17f63
--- /dev/null
+++ b/BuildTools/djsbuild.cmd
@@ -0,0 +1,136 @@
+@echo off
+
+rem Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+rem Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+rem files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+rem modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+rem Software is furnished to do so, subject to the following conditions:
+rem 
+rem The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+rem 
+rem THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+rem WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+rem COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+rem ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+setlocal    
+
+
+if "%DJSROOT%" == "" (
+  echo Please set the DJSROOT environment variable for the build process.
+  exit /b 1
+)
+
+if "%DJSOUT%" == "" (
+  echo Please set the DJSOUT environment variable for the build process.
+  exit /b 1
+)
+
+rem Build overview:
+rem 1. Call msbuild to generate the webapplication bin directory.
+rem 2. All solution files are copied or merged into the output directory.
+rem 3. Internal references are removed from the merged file (eg, ODATA_INTERNAL).
+rem    This produces datajs.js.
+rem 4. The minifier is run. This produces datajs.min.js.
+rem 5. The files are renamed to the version-specific filenames.
+
+echo Invoking msbuild...
+where /Q msbuild.exe
+if errorlevel 1 (
+    echo Please add the path to msbuild.exe to the Path environment variable for the build process.
+    exit /b 1
+)
+msbuild /nologo /clp:DisableConsoleColor /target:rebuild "%DJSROOT%\JSLib\JSLib.sln"
+
+echo Building internal datajs version...
+cscript "%DJSROOT%\BuildTools\djsbuildfile.wsf" //Nologo /build-solution:true /out:"%DJSOUT%" "%DJSROOT%\JSLib\JSLib.sln"
+if errorlevel 1 (
+  exit /b %errorlevel%
+)
+
+if exist "%DJSOUT%\jslib.sln\src\datajs.merged.js" (
+  del "%DJSOUT%\jslib.sln\src\datajs.merged.js"
+  if errorlevel 1 (
+    exit /b %errorlevel%
+  )
+)
+
+ren "%DJSOUT%\jslib.sln\src\datajs.js" datajs.merged.js
+if errorlevel 1 (
+  exit /b %errorlevel%
+)
+
+echo.
+echo Removing internal references...
+cscript "%DJSROOT%\BuildTools\djsbuildfile.wsf" //Nologo /remove-internals:true /out:"%DJSOUT%\jslib.sln\src\datajs.js" "%DJSOUT%\jslib.sln\src\datajs.merged.js"
+if errorlevel 1 (
+  exit /b %errorlevel%
+)
+
+echo.
+echo Minifying output file...
+if exist "%DJSROOT%\BuildTools\ajaxmin\AjaxMin.exe" (
+  "%DJSROOT%\BuildTools\ajaxmin\AjaxMin.exe" -JS -debug:false -analyze -clobber:true "%DJSOUT%\jslib.sln\src\datajs.js" -out "%DJSOUT%\jslib.sln\src\datajs.min.body.js" > "%DJSOUT%\jslib.sln\src\datajs.js.log" 2>&1
+  if errorlevel 1 (
+    echo Error when running AjaxMin.exe on "%DJSOUT%\jslib.sln\src\datajs.js"
+    echo See log at "%DJSOUT%\jslib.sln\src\datajs.js.log"
+    exit /b 1
+  )
+
+  copy "%DJSROOT%\BuildTools\djslicense.js" /B +"%DJSOUT%\jslib.sln\src\datajs.min.body.js" /B "%DJSOUT%\jslib.sln\src\datajs.min.js" /B
+  del /Q "%DJSOUT%\jslib.sln\src\datajs.min.body.js"
+  
+) else (
+  type "%DJSROOT%\BuildTools\ajaxmin\readme.txt"
+  echo.
+  echo Using non-minified file instead.
+  copy /y "%DJSOUT%\jslib.sln\src\datajs.js" "%DJSOUT%\jslib.sln\src\datajs.min.js"
+)
+
+
+call "%DJSROOT%\BuildTools\djsbuildver.cmd"
+
+if exist "%DJSOUT%\jslib.sln\src\datajs-%DJSVER%.js" (
+  del /q "%DJSOUT%\jslib.sln\src\datajs-%DJSVER%.js"
+  if errorlevel 1 (
+    exit /b %errorlevel%
+  )
+)
+
+if exist "%DJSOUT%\jslib.sln\src\datajs-%DJSVER%.min.js" (
+  del /q "%DJSOUT%\jslib.sln\src\datajs-%DJSVER%.min.js"
+  if errorlevel 1 (
+    exit /b %errorlevel%
+  )
+)
+
+ren "%DJSOUT%\jslib.sln\src\datajs.js" datajs-%DJSVER%.js
+ren "%DJSOUT%\jslib.sln\src\datajs.min.js" datajs-%DJSVER%.min.js
+
+echo.
+echo Redirecting unit tests to internal file...
+cscript "%DJSROOT%\BuildTools\djspatchtests.wsf" //Nologo /in:"%DJSOUT%\jslib.sln\tests" /replace-script:"../src/datajs.merged.js"
+if errorlevel 1 (
+   exit /b %errorlevel%
+)
+
+echo.
+echo Copying bin folder...
+robocopy "%DJSROOT%\JSLib\bin" "%DJSOUT%\jslib.sln\bin" /E /R:10 1>NUL
+
+if errorlevel 8 (
+    exit /b %errorlevel%
+)
+
+if errorlevel 16 (
+    exit /b %errorlevel%
+)
+
+echo.
+echo The built files are:
+echo %DJSOUT%\jslib.sln\src\datajs-%DJSVER%.js     - development version
+echo %DJSOUT%\jslib.sln\src\datajs-%DJSVER%.min.js - minified version
+
+endlocal
+
+exit /b 0
\ No newline at end of file
diff --git a/BuildTools/djsbuildfile.js b/BuildTools/djsbuildfile.js
new file mode 100644
index 0000000..d31ae85
--- /dev/null
+++ b/BuildTools/djsbuildfile.js
@@ -0,0 +1,186 @@
+/// <reference path="djscommon.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+var markerFormats = {};
+
+markerFormats[".js"] = "// MARKER";
+markerFormats[".html"] = "<!-- MARKER -->";
+markerFormats[".htm"] = markerFormats[".html"];
+
+function MakeMarker(ext, marker) {
+    /// <summary>Builds the appropriate marker string based on the file extension.</summary>
+    /// <param name="ext" type="String">Extension of the file for which the marker string is going to be generated.</param>
+    /// <param name="marker" type="String">Text to be contained in the marker.</param>
+    /// <returns type="String">Specific marker string.</returns>
+
+    var format = markerFormats[ext.toLowerCase()];
+    if (format) {
+        return format.replace("MARKER", marker);
+    }
+    return null;
+}
+
+function BuildFromProject(path, outBasePath) {
+    // Read the csproj.
+    WScript.Echo("Reading project " + path);
+    var xml = new ActiveXObject("Msxml2.DOMDocument.6.0");
+    xml.load(path);
+    xml.setProperty("SelectionNamespaces", "xmlns:msb='http://schemas.microsoft.com/developer/msbuild/2003'");
+    xml.setProperty("SelectionLanguage", "XPath");
+
+    var files = xml.selectNodes("//msb:ItemGroup/*[not(self::msb:Reference)]/@Include");
+    var fileIncludes = {};
+    var i, len, filePath;
+
+    // Get file dependencies.
+    for (i = 0, len = files.length; i < len; i++) {
+        filePath = PathCombine(PathGetDirectory(path), files[i].value);
+        if (FileExists(filePath)) {
+            var includes = GetFileIncludes(filePath);
+            var j, includeLen;
+            for (j = 0, includeLen = includes.length; j < includeLen; j++) {
+                fileIncludes[includes[j]] = {};
+            }
+        } else if (!FolderExists(filePath)) {
+            throw { message: "path doesn't exist " + filePath };
+        }
+    }
+
+    // Build the files that are not in the dependency list.
+    for (i = 0, len = files.length; i < len; i++) {
+        filePath = PathCombine(PathGetDirectory(path), files[i].value);
+        var outputPath = PathCombine(outBasePath, files[i].value);
+        if (!fileIncludes[filePath] && !FolderExists(filePath)) {
+            BuildFile(filePath, outputPath);
+        }
+    }
+}
+
+function GetFileIncludes(path) {
+    var content = ReadAllTextFile(path);
+    var lines = StringSplit(content, "\r\n");
+    var result = [];
+    var i, len;
+    for (i = 0, len = lines.length; i < len; i++) {
+        var line = lines[i];
+        var includeIndex = line.indexOf("// INCLUDE:");
+        if (includeIndex !== -1) {
+            var anotherPath = line.substr(includeIndex + 11);
+            anotherPath = anotherPath.replace(/^\s+|\s+$/g, "");
+            if (!FileExists(anotherPath)) {
+                anotherPath = PathGetDirectory(path) + "\\" + anotherPath;
+            }
+            result.push(GetFileIncludes(anotherPath));
+            result.push(anotherPath);
+        }
+    }
+
+    return result;
+}
+
+// TODO: provide support for composing relative paths.
+function BuildFromSln(path, outBasePath) {
+    WScript.Echo("Reading solution " + path);
+    var outPath = PathCombine(outBasePath, PathGetFileName(path));
+    var regEx = /(Project\("[^"]+"\)\s*=\s*)"([^"]+)"\s*,\s*"([^"]+)"/;
+    var content = ReadAllTextFile(path);
+    var lines = StringSplit(content, "\r\n");
+    var i, len;
+    for (i = 0, len = lines.length; i < len; i++) {
+        var matches = regEx.exec(lines[i]);
+        if (matches) {
+            var projectPath = PathCombine(PathGetDirectory(path), matches[3]);
+            BuildFromProject(projectPath, outPath);
+        }
+    }
+}
+
+function BuildFile(path, outPath) {
+    /// <summary>Builds a JavaScript file. </summary>
+    /// <param name="path" type="String">Path to the file whose content is going to be built.</param>
+    /// <param name="outPath" type="String">Path of the built file.</param>
+
+    if (!FileExists(path)) {
+        throw { message: "File does not exist: " + inName };
+    }
+
+    CreateFolderIfMissing(PathGetDirectory(outPath));
+    switch (PathGetExtension(path).toLowerCase()) {
+        case ".js":
+        case ".htm":
+        case ".html":
+            WScript.Echo("building file: " + path);
+            var built = BuildFileForContent(path, false);
+            SaveTextToFile(built, outPath);
+            break;
+        default:
+            WScript.Echo("copying file: " + path);
+            CopyFile(path, outPath, true);
+            break;
+    }
+}
+
+function BuildFileForContent(path, contentOnly) {
+    var ext = PathGetExtension(path);
+    var includeCallback = function (line, inContent) {    
+        var includeIndex = line.indexOf(MakeMarker(ext, "INCLUDE: "));
+        if (includeIndex !== -1) {
+            var anotherPath = line.substr(includeIndex + 11);
+            anotherPath = anotherPath.replace(/^\s+|\s+$/g, "");
+            if (!FileExists(anotherPath)) {
+                anotherPath = PathGetDirectory(path) + "\\" + anotherPath;
+            }
+
+            return BuildFileForContent(anotherPath, true);
+        } else {
+            return (inContent) ? line : null;
+        }
+    };
+
+    return ExtractContentsBetweenMarkers(path, contentOnly, /*isExclusion*/ false, 
+        MakeMarker(ext, "CONTENT START"), MakeMarker(ext, "CONTENT END"), includeCallback);
+}
+
+function RemoveInternals(inName, outName) {
+    var ext = PathGetExtension(inName);
+    var includeCallback = function (line, inContent) {
+        return (line.indexOf("djsassert") === -1) ? line : null;
+    };
+    var content = ExtractContentsBetweenMarkers(inName, true, /*isExclusion*/ true,
+        MakeMarker(ext, "DATAJS INTERNAL START"), MakeMarker(ext, "DATAJS INTERNAL END"), includeCallback);
+    
+    SaveTextToFile(content, outName);
+}
+
+RunAndQuit(function () {
+    var args = WScript.Arguments;
+    var inName = WScript.Arguments.Unnamed(0);
+    if (!inName) {
+        throw { message: "no input specified" };
+    }
+
+    var outName = WScript.Arguments.Named("out");
+    if (!outName) {
+        throw { message: "no output specified" };
+    }
+
+    if (CheckScriptFlag("build-solution")) {
+        BuildFromSln(inName, outName);
+    }
+    if (CheckScriptFlag("remove-internals")) {
+        RemoveInternals(inName, outName);
+    }
+});
\ No newline at end of file
diff --git a/BuildTools/djsbuildfile.wsf b/BuildTools/djsbuildfile.wsf
new file mode 100644
index 0000000..e8006f5
--- /dev/null
+++ b/BuildTools/djsbuildfile.wsf
@@ -0,0 +1,17 @@
+<package>
+  <job>
+    <runtime>
+      <description>
+      This script is used to process files from the datajs project
+      into a variety of output script files.
+      </description>
+      <unnamed name="in" helpstring="Input to process" type="string" required="true" />
+      <named name="out" helpstring="Path to directory for output files." type="string" required="true" />
+      <named name="build-solution" helpstring="Builds the solution input by coalescing scripts." type="string" required="false" />
+      <named name="remove-internals" helpstring="Removes internal references from the input." type="string" required="false" />
+      <example>cscript djsbuildfile.wsf //Nologo /out:%djsroot%\JSLib\JSLib.sln /in:%djsroot%\out</example>
+    </runtime>
+    <script language="JScript" src="djscommon.js" />
+    <script language="JScript" src="djsbuildfile.js" />
+  </job>
+</package>
\ No newline at end of file
diff --git a/BuildTools/djsbuildver.cmd b/BuildTools/djsbuildver.cmd
new file mode 100644
index 0000000..279e499
--- /dev/null
+++ b/BuildTools/djsbuildver.cmd
@@ -0,0 +1,16 @@
+@echo off
+
+rem Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+rem Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+rem files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+rem modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+rem Software is furnished to do so, subject to the following conditions:
+rem 
+rem The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+rem 
+rem THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+rem WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+rem COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+rem ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+set DJSVER=1.1.2
\ No newline at end of file
diff --git a/BuildTools/djscommon.js b/BuildTools/djscommon.js
new file mode 100644
index 0000000..ca779bb
--- /dev/null
+++ b/BuildTools/djscommon.js
@@ -0,0 +1,522 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+//
+// References:
+//
+// JScript Language Reference
+// http://msdn2.microsoft.com/en-us/library/yek4tbz0
+//
+// Windows Script Host Object Model
+// http://msdn2.microsoft.com/en-us/library/a74hyyw0
+//
+// Script Runtime
+// http://msdn2.microsoft.com/en-us/library/hww8txat.aspx
+//
+
+function ArrayAny(arr, callback) {
+    /// <summary>Checks whether any element in an array satisfies a predicate.</summary>
+    /// <param name="arr" type="Array">Array to operate on.</param>
+    /// <param name="callback" type="Function">Function to test with element and index, returning true or false.</param>
+    /// <returns type="Boolean">true if 'callback' returns true for any element; false otherwise.</returns>
+    for (var i = 0; i < arr.length; i++) {
+        if (callback(arr[i], i)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+function ArrayWhere(arr, callback) {
+    /// <summary>Returns the elements in an array that satisfy a predicate.</summary>
+    /// <param name="arr" type="Array">Array to operate on.</param>
+    /// <param name="callback" type="Function">Function to test with element and index, returning true or false.</param>
+    /// <returns type="Array">Array of elements from arr that satisfy the predicate.</returns>
+
+    var result = [];
+
+    for (var i = 0; i < arr.length; i++) {
+        if (callback(arr[i], i)) {
+            result.push(arr[i]);
+        }
+    }
+    return result;
+}
+
+function ArrayForEach(arr, callback) {
+    /// <summary>Invokes a callback for each element in the array.</summary>
+    /// <param name="arr" type="Array">Array to operate on.</param>
+    /// <param name="callback" type="Function">Function to invoke with element and index.</param>
+    for (var i = 0; i < arr.length; i++) {
+        callback(arr[i], i);
+    }
+}
+
+function CheckScriptFlag(name) {
+    /// <summary>Checks whether a script argument was given with true or false.</summary>
+    /// <param name="name" type="String">Argument name to check.</param>
+    /// <returns type="Boolean">
+    /// true if the argument was given witha value of 'true' or 'True'; false otherwise.
+    /// </returns>
+    var flag = WScript.Arguments.Named(name);
+    if (!flag) {
+        return false;
+    }
+
+    return flag === "true" || flag === "True";
+}
+
+function CreateFolderIfMissing(path) {
+    /// <summary>Creates a folder if it doesn't exist.</summary>
+    /// <param name="path" type="String">Path to folder to create.</param>
+    /// <remarks>This function will write out to the console on creation.</remarks>
+    if (!path) return;
+    var parent = PathGetDirectory(path);
+    var fso = new ActiveXObject("Scripting.FileSystemObject");
+    if (!fso.FolderExists(parent)) {
+        CreateFolderIfMissing(parent);
+    }
+    if (!fso.FolderExists(path)) {
+        WScript.Echo("Creating " + path + "...");
+        fso.CreateFolder(path);
+    }
+}
+
+function DeleteFile(path, force) {
+    /// <summary>Deletes a file.</summary>
+    /// <param name="path" type="String">Path to the file.</param>
+    /// <param name="force" type="Boolean">Whether to delete the file even if it has the read-only attribute set.</param>
+
+    var fso = new ActiveXObject("Scripting.FileSystemObject");
+    fso.DeleteFile(path, force);
+}
+
+function DeleteFolder(path, force) {
+    /// <summary>Deletes a folder.</summary>
+    /// <param name="path" type="String">Path to the folder.</param>
+    /// <param name="force" type="Boolean">Whether to delete the folder even if it has the read-only attribute set.</param>
+
+    var fso = new ActiveXObject("Scripting.FileSystemObject");
+    fso.DeleteFolder(path, force);
+}
+
+function CopyFolder(source, dest, overwrite) {
+    /// <summary>Recursively copies a folder and its contents from source to dest.</summary>
+    /// <param name="source" type="String">Path to the source folder location.</param>
+    /// <param name="dest" type="String">Path to the destination folder location.</param>
+    /// <param name="overwrite" type="Boolean">Whether to overwrite a folder in the destination location.</param>
+
+    var fso = new ActiveXObject("Scripting.FileSystemObject");
+    fso.CopyFolder(source, dest, overwrite);
+}
+
+
+function CopyFile(source, dest, overwrite) {
+    /// <summary>Copies a file from source to dest.</summary>
+    /// <param name="source" type="String">Path to the source file location.</param>
+    /// <param name="dest" type="String">Path to the destination file location.</param>
+    /// <param name="overwrite" type="Boolean">Whether to overwrite a file in the destination location.</param>
+
+    var fso = new ActiveXObject("Scripting.FileSystemObject");
+
+    if (overwrite && fso.FileExists(dest)) {
+        var f = fso.getFile(dest);
+        f.attributes = 0;
+    }
+
+    fso.CopyFile(source, dest, overwrite);
+}
+
+function ExtractContentsBetweenMarkers(path, contentOnly, isExclusion, startMarker, endMarker, callback) {
+    /// <summary>
+    /// Extracts the lines from the 'path' text file between the start and end markers.
+    /// </summary>
+    /// <param name="path" type="String">Path to file.</param>
+    /// <param name="contentOnly" type="Boolean">
+    /// true to skip everything until it's found between markers, false to start including everything from the start.
+    /// </param>
+    /// <param name="isExclusion" type="Boolean">
+    /// false if the 'extraction' means keeping the content; true if it means not excluding it from the result.
+    /// </param>
+    /// <param name="startMarker" type="String">Line content to match for content start.</param>
+    /// <param name="endMarker" type="String">Line content to match for content end.</param>
+    /// <param name="callback" type="Function" mayBeNull="true">
+    /// If true, then this function is called for every line along with the inContent flag
+    /// before the line is added; the called function may return a line
+    /// to be added in its place, null to skip processing.
+    /// </param>
+    /// <returns type="String">The string content of the file.</returns>
+
+    var content = ReadAllTextFile(path);
+    return ExtractContentsBetweenMarkersForText(content, contentOnly, isExclusion, startMarker, endMarker, callback);
+}
+
+function ExtractContentsBetweenMarkersForText(content, contentOnly, isExclusion, startMarker, endMarker, callback) {
+    /// <summary>
+    /// Extracts the lines from the specified text between the start and end markers.
+    /// </summary>
+    /// <param name="content" type="String">Text to process.</param>
+    /// <param name="contentOnly" type="Boolean">
+    /// true to skip everything until it's found between markers, false to start including everything from the start.
+    /// </param>
+    /// <param name="isExclusion" type="Boolean">
+    /// false if the 'extraction' means keeping the content; true if it means not excluding it from the result.
+    /// </param>
+    /// <param name="startMarker" type="String">Line content to match for content start.</param>
+    /// <param name="endMarker" type="String">Line content to match for content end.</param>
+    /// <param name="callback" type="Function" mayBeNull="true">
+    /// If true, then this function is called for every line along with the inContent flag
+    /// before the line is added; the called function may return a line
+    /// to be added in its place, null to skip processing.
+    /// </param>
+    /// <returns type="String">The extracted content.</returns>
+
+    var inContent = contentOnly === false;
+    var lines = StringSplit(content, "\r\n");
+    var result = [];
+    var i, len;
+    for (i = 0, len = lines.length; i < len; i++) {
+        var line = lines[i];
+        var contentStartIndex = line.indexOf(startMarker);
+        if (inContent === false && contentStartIndex !== -1) {
+            inContent = true;
+            continue;
+        }
+
+        var contentEndIndex = line.indexOf(endMarker);
+        if (inContent === true && contentEndIndex !== -1) {
+            inContent = false;
+            continue;
+        }
+
+        if (inContent !== isExclusion) {
+            if (callback) {
+                var callbackResult = callback(line, inContent);
+                if (callbackResult !== null && callbackResult !== undefined) {
+                    result.push(callbackResult);
+                }
+            } else {
+                result.push(line);
+            }
+        }
+    }
+
+    return result.join("\r\n");
+}
+
+function FolderExists(path) {
+    /// <summary>Checks whether the specified directory exists.</summary>
+    var fso = new ActiveXObject("Scripting.FileSystemObject");
+    if (fso.FolderExists(path)) {
+        return true;
+    }
+    else {
+        return false;
+    }
+}
+
+function FileExists(path) {
+    /// <summary>Checks whether the specified file exists.</summary>
+    var fso = new ActiveXObject("Scripting.FileSystemObject");
+    if (fso.FileExists(path)) {
+        return true;
+    }
+    else {
+        return false;
+    }
+}
+
+function GetEnvironmentVariable(name) {
+    /// <summary>Gets the value of the specified environment variable.</summary>
+    /// <param name="name" type="String">Name of the variable value to get.</param>
+    /// <returns type="String">Value for the given environment variable; null if undefined.</returns>
+    var shell = new ActiveXObject("WScript.Shell");
+    var result = shell.ExpandEnvironmentStrings("%" + name + "%");
+    if (result == "%" + name + "%") {
+        result = null;
+    }
+
+    return result;
+}
+
+function GetFilesRecursive(path) {
+    /// <summary>Gets all file names under the specified directory path.</summary>
+    /// <param name="path" type="String">Path to directory.</param>
+    /// <returns type="Array">Array of all file names under path.</returns>
+
+    var result = [];
+    var fso = new ActiveXObject("Scripting.FileSystemObject");
+    var pending = [path];
+
+    while (pending.length) {
+        var item = pending.pop();
+        var folder = fso.GetFolder(item);
+        for (var files = new Enumerator(folder.Files); !files.atEnd(); files.moveNext()) {
+            result.push(files.item().Path);
+        }
+
+        for (var subFolders = new Enumerator(folder.SubFolders); !subFolders.atEnd(); subFolders.moveNext()) {
+            pending.push(subFolders.item().Path);
+        }
+    }
+
+    return result;
+}
+
+function GetRelativePathFrom(startPath, endPath) {
+    if (startPath[startPath.length - 1] !== "\\") {
+        startPath += "\\";
+    }
+
+    if (startPath.length > endPath.length) {
+        throw { message: "traversing up NYI" };
+    }
+
+    return endPath.substr(startPath.length);
+}
+
+function MatchesMask(file, mask) {
+    if (!mask) {
+        return false;
+    }
+
+    if (file === mask) {
+        return true;
+    }
+
+    if (mask.substr(0, 1) === "*") {
+        var rest = mask.substr(1);
+        return file.substr(file.length - rest.length) === rest;
+    } else if (mask.substr(mask.length - 1) === "*") {
+        var end = mask.substr(0, mask.length - 1);
+        return file.substr(0, end.length) === end;
+    }
+
+    return false;
+}
+
+function PathGetDirectory(path) {
+    /// <summary>
+    /// Returns the directory of the specified path string (excluding the trailing "\\");
+    /// empty if there is no path.
+    /// </summary>
+
+    var l = path.length;
+    var startIndex = l;
+    while (--startIndex >= 0) {
+        var ch = path.substr(startIndex, 1);
+        if (ch == "\\") {
+            if (startIndex === 0) {
+                return "";
+            } else {
+                return path.substr(0, startIndex);
+            }
+        }
+    }
+
+    return "";
+}
+
+function PathGetFileName(path) {
+    /// <summary>
+    /// Returns the file name for the specified path string; empty if there is no
+    /// directory information.
+    /// </summary>
+    var l = path.length;
+    var startIndex = l;
+    while (--startIndex >= 0) {
+        var ch = path.substr(startIndex, 1);
+        if (ch == "\\" || ch == "/" || ch == ":") {
+            return path.substr(startIndex, l - startIndex);
+        }
+    }
+    return "";
+}
+
+function PathGetExtension(path) {
+    /// <summary>
+    /// Returns the extension of the specified path string (including the ".");
+    /// empty if there is no extension.
+    /// </summary>
+    var l = path.length;
+    var startIndex = l;
+    while (--startIndex >= 0) {
+        var ch = path.substr(startIndex, 1);
+        if (ch == ".") {
+            if (startIndex != (l - 1)) {
+                return path.substr(startIndex, l - startIndex);
+            }
+            return "";
+        }
+        else if (ch == "\\" || ch == ":") {
+            break;
+        }
+    }
+    return "";
+}
+
+function ReadAllTextFile(path) {
+    /// <summary>Reads all the content of the file into a string.</summary>
+    /// <param name="path" type="String">File name to read from.</param>
+    /// <returns type="String">File contents.</returns>
+    var ForReading = 1, ForWriting = 2;
+    var fso = new ActiveXObject("Scripting.FileSystemObject");
+    var file = fso.OpenTextFile(path, ForReading);
+    try {
+        var result;
+        if (file.AtEndOfStream) {
+            result = "";
+        } else {
+            result = file.ReadAll();
+        }
+    } finally {
+        file.Close();
+    }
+
+    return result;
+}
+
+function ReadXmlFile(path) {
+    /// <summary>Reads an XML document from the specified path.</summary>
+    /// <param name="path" type="String">Path to file on disk.</param>
+    /// <returns>A DOMDocument with the contents of the given file.</returns>
+    var result = new ActiveXObject("Msxml2.DOMDocument.6.0");
+    result.async = false;
+    result.load(path);
+    if (result.parseError.errorCode !== 0) {
+        throw { message: "Error reading '" + path + "': " + result.parseError.reason };
+    }
+
+    return result;
+}
+
+// Runs the specified function catching exceptions and quits the current script.
+function RunAndQuit(f) {
+    try {
+        f();
+    }
+    catch (e) {
+        // An error with 'statusCode' defined will avoid the usual error dump handling.
+        if (e.statusCode !== undefined) {
+            if (e.message) {
+                WScript.Echo(e.message);
+            }
+
+            WScript.Quit(e.statusCode);
+        }
+
+        WScript.Echo("Error caught while running this function:");
+        WScript.Echo(f.toString());
+        WScript.Echo("Error details:");
+        if (typeof (e) == "object" && e.toString() == "[object Object]" || e.toString() === "[object Error]") {
+            for (var p in e) WScript.Echo(" " + p + ": " + e[p]);
+        }
+        else {
+            WScript.Echo(e);
+        }
+
+        WScript.Quit(1);
+    }
+    WScript.Quit(0);
+}
+
+function RunConsoleCommand(strCommand, timeout, retry) {
+    /// <summary>Runs a command and waits for it to exit.</summary>
+    /// <param name="strCommand" type="String">Command to run.</param>
+    /// <param name="timeout" type="int">Timeout in seconds.</param>
+    /// <param name="timeout" type="bool">Boolean specifying whether to retry on timeout or not.</param>
+    /// <returns type="Array">An array with stdout in 0, stderr in 1 and exit code in 2. Forced
+    /// termination sets 2 to 1.</returns>
+    var WshShell = new ActiveXObject("WScript.Shell");
+    var result = new Array(3);
+    var oExec = WshShell.Exec(strCommand);
+    var counter = 0;
+
+    if (timeout) {
+        // Status of 0 means the process is still running
+        while (oExec.Status === 0 && counter < timeout) {
+            WScript.Sleep(1000);
+            counter++;
+        }
+
+        if (timeout === counter && oExec.Status === 0) {
+            WScript.Echo("Forcefully terminating " + strCommand + " after " + timeout + " seconds.");
+            oExec.Terminate();
+            result[2] = 1;
+            if (retry) {
+                return RunConsoleCommand(strCommand, timeout, false);
+            }
+        }
+    }
+
+    result[0] = oExec.StdOut.ReadAll();
+    result[1] = oExec.StdErr.ReadAll();
+
+    if (!result[2]) {
+        result[2] = oExec.ExitCode;
+    }
+
+    return result;
+}
+
+function SaveTextToFile(content, path) {
+    /// <summary>Saves text content into a file.</summary>
+    /// <param name="content" type="String">Content to save.</param>
+    /// <param name="path" type="String">Path of file to save into.</param>
+    var ForReading = 1, ForWriting = 2;
+    var fso = new ActiveXObject("Scripting.FileSystemObject");
+    var file = fso.OpenTextFile(path, ForWriting, true);
+    file.Write(content);
+    file.Close();
+}
+
+function StringSplit(strLine, strSeparator) {
+    /// <summary>Splits a string into a string array.</summary>
+    var result = new Array();
+    var startIndex = 0;
+    var resultIndex = 0;
+    while (startIndex < strLine.length) {
+        var endIndex = strLine.indexOf(strSeparator, startIndex);
+        if (endIndex == -1) {
+            endIndex = strLine.length;
+        }
+        result[resultIndex] = strLine.substring(startIndex, endIndex);
+        startIndex = endIndex + strSeparator.length;
+        resultIndex++;
+    }
+    return result;
+}
+
+function PathCombine(path1, path2) {
+    if (path1.charAt(path1.length - 1) !== "\\") {
+        return path1 + "\\" + path2;
+    }
+    return path1 + path2;
+}
+
+function RemoveReadOnlyAttribute(path) {
+    /// <summary>Removes the read-only attribute on the specified file.</summary>
+    /// <param name="path" type="String">Path to the file.</param>
+    var fso = new ActiveXObject("Scripting.FileSystemObject");
+    var f = fso.getFile(path);
+    if (1 === (f.attributes & 1)) {
+        f.attributes = (f.attributes & ~1);
+    }
+}
+
+function WriteXmlFile(document, path) {
+    /// <summary>Write an XML document to the specified path.</summary>
+    /// <param name="path" type="String">Path to file on disk.</param>
+    document.save(path);
+}
\ No newline at end of file
diff --git a/BuildTools/djslicense.js b/BuildTools/djslicense.js
new file mode 100644
index 0000000..a6bfe15
--- /dev/null
+++ b/BuildTools/djslicense.js
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/BuildTools/djspatchtests.js b/BuildTools/djspatchtests.js
new file mode 100644
index 0000000..ef977f6
--- /dev/null
+++ b/BuildTools/djspatchtests.js
@@ -0,0 +1,55 @@
+/// <reference path="djscommon.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// This script will search for a particular text pattern in an html file and replace its first occurrence with the value defined in 
+// the replace-script parameter. All other lines with subsequent occurrences of the pattern will be removed from the file. 
+
+function Main() {
+    var shell = new ActiveXObject("WScript.Shell");
+    var inputPath = shell.ExpandEnvironmentStrings(WScript.Arguments.Named("in"));
+    var replaceScript = WScript.Arguments.Named("replace-script");
+    var removePattern = /\.\.\/src\/.*\.js/;
+
+    var files = ArrayWhere(GetFilesRecursive(inputPath), function (file) {
+        return MatchesMask(file, "*.htm") || MatchesMask(file, "*.html");
+    });
+
+    ArrayForEach(files, function (file) {
+        var content = ReadAllTextFile(file);
+        var lines = StringSplit(content, "\r\n");
+        var result = [];
+        var included = false;
+        ArrayForEach(lines, function (line) {
+            if (line.match(removePattern)) {
+                if (!included) {
+                    included = true;
+                    line = line.replace(removePattern, replaceScript);
+                    result.push(line);
+                }
+            } else {
+                result.push(line);
+            }
+        });
+
+        if (included) {
+            content = result.join("\r\n");
+            WScript.Echo("Patched " + file);
+            RemoveReadOnlyAttribute(file);
+            SaveTextToFile(content, file);
+        }
+    });
+}
+
+RunAndQuit(Main);
\ No newline at end of file
diff --git a/BuildTools/djspatchtests.wsf b/BuildTools/djspatchtests.wsf
new file mode 100644
index 0000000..7df918a
--- /dev/null
+++ b/BuildTools/djspatchtests.wsf
@@ -0,0 +1,14 @@
+<package>
+  <job>
+    <runtime>
+      <description>
+      This script is used to patch the html files so they can use the build output.
+      </description>
+      <named name="in" helpstring="Path to directory with the html test files to patch." type="string" required="true" />
+      <named name="replace-script" helpstring="Target script reference." type="string" required="true" />
+      <example>cscript djspatchtests.wsf //Nologo /in:%DJSOUT%\jslib.sln\tests /replace-script:"../src/datajs-%DJSVER%.js"</example>
+    </runtime>
+    <script language="JScript" src="djscommon.js" />
+    <script language="JScript" src="djspatchtests.js" />
+  </job>
+</package>
\ No newline at end of file
diff --git a/JSLib/JSLib.csproj b/JSLib/JSLib.csproj
new file mode 100644
index 0000000..bff96ae
--- /dev/null
+++ b/JSLib/JSLib.csproj
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+	<PropertyGroup>
+		<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+		<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+		<ProductVersion>
+		</ProductVersion>
+		<SchemaVersion>2.0</SchemaVersion>
+		<ProjectGuid>{73ADF1A7-613B-4679-885B-2AE4AFAA9A6A}</ProjectGuid>
+		<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
+		<OutputType>Library</OutputType>
+		<AppDesignerFolder>Properties</AppDesignerFolder>
+		<RootNamespace>JSLib</RootNamespace>
+		<AssemblyName>JSLib</AssemblyName>
+		<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+		<UseIISExpress>false</UseIISExpress>
+	</PropertyGroup>
+	<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+		<DebugSymbols>true</DebugSymbols>
+		<DebugType>full</DebugType>
+		<Optimize>false</Optimize>
+		<OutputPath>bin\</OutputPath>
+		<DefineConstants>DEBUG;TRACE</DefineConstants>
+		<ErrorReport>prompt</ErrorReport>
+		<WarningLevel>4</WarningLevel>
+	</PropertyGroup>
+	<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+		<DebugType>pdbonly</DebugType>
+		<Optimize>true</Optimize>
+		<OutputPath>bin\</OutputPath>
+		<DefineConstants>TRACE</DefineConstants>
+		<ErrorReport>prompt</ErrorReport>
+		<WarningLevel>4</WarningLevel>
+	</PropertyGroup>
+	<ItemGroup>
+		<!-- Product code. -->
+		<Content Include="src\cache-source.js"/>
+		<Content Include="src\odata-gml.js"/>
+		<Content Include="src\utils.js"/>
+		<Content Include="src\odata-json-light.js"/>
+		<Content Include="src\datajs.js"/>
+		<Content Include="src\cache.js"/>
+		<Content Include="src\deferred.js"/>
+		<Content Include="src\odata.js"/>
+		<Content Include="src\odata-batch.js"/>
+		<Content Include="src\odata-handler.js"/>
+		<Content Include="src\odata-utils.js"/>
+		<Content Include="src\odata-xml.js"/>
+		<Content Include="src\odata-metadata.js"/>
+		<Content Include="src\odata-json.js"/>
+		<Content Include="src\odata-atom.js"/>
+		<Content Include="src\odata-net.js"/>
+		<Content Include="src\store-dom.js"/>
+		<Content Include="src\store-indexeddb.js"/>
+		<Content Include="src\store-memory.js"/>
+		<Content Include="src\store.js"/>
+		<Content Include="src\xml.js"/>
+		<Content Include="tests\common\djstest.js"/>
+		<Content Include="tests\common\ODataReadOracle.js"/>
+		<Content Include="tests\common\ODataReadOracle.svc"/>
+		<Content Include="tests\common\TestLogger.svc"/>
+		<Content Include="tests\common\TestSynchronizerClient.js"/>
+		<Content Include="tests\endpoints\FoodStoreDataServiceV3.svc"/>
+		<Content Include="tests\endpoints\FoodStoreDataService.svc"/>
+		<Content Include="tests\endpoints\FoodStoreDataServiceV2.svc"/>
+		<Content Include="tests\endpoints\web.config"/>
+		<Content Include="tests\odata-atom-tests.js"/>
+		<Content Include="tests\odata-qunit-tests.htm"/>
+		<Content Include="tests\run-tests.wsf"/>
+		<Content Include="tests\test-list.js"/>
+		<!-- Configuration file for the web project. -->
+		<Content Include="Web.config"/>
+	</ItemGroup>
+	<ItemGroup>
+		<Compile Include="tests\code\AtomReader.cs"/>
+		<Compile Include="tests\code\CsdlReader.cs"/>
+		<Compile Include="tests\code\JsDate.cs"/>
+		<Compile Include="tests\code\JsonObject.cs"/>
+		<Compile Include="tests\code\ReaderUtils.cs"/>
+		<Compile Include="tests\code\ReflectionDataContext.cs"/>
+	</ItemGroup>
+	<ItemGroup>
+	</ItemGroup>
+	<ItemGroup>
+		<Reference Include="Microsoft.Data.Edm, Version=5.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+			<HintPath>packages\Microsoft.Data.Edm.5.1.0\lib\net40\Microsoft.Data.Edm.dll</HintPath>
+		</Reference>
+		<Reference Include="Microsoft.Data.OData, Version=5.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+			<HintPath>packages\Microsoft.Data.OData.5.1.0\lib\net40\Microsoft.Data.OData.dll</HintPath>
+		</Reference>
+		<Reference Include="Microsoft.Data.Services, Version=5.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+			<HintPath>packages\Microsoft.Data.Services.5.1.0\lib\net40\Microsoft.Data.Services.dll</HintPath>
+		</Reference>
+		<Reference Include="Microsoft.Data.Services.Client, Version=5.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+			<HintPath>packages\Microsoft.Data.Services.Client.5.1.0\lib\net40\Microsoft.Data.Services.Client.dll</HintPath>
+		</Reference>
+		<Reference Include="System"/>
+		<Reference Include="System.Net"/>
+		<Reference Include="System.Runtime.Serialization"/>
+		<Reference Include="System.ServiceModel"/>
+		<Reference Include="System.ServiceModel.Activation"/>
+		<Reference Include="System.ServiceModel.Web"/>
+		<Reference Include="System.Spatial, Version=5.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+			<HintPath>packages\System.Spatial.5.1.0\lib\net40\System.Spatial.dll</HintPath>
+		</Reference>
+		<Reference Include="System.Web.Extensions"/>
+		<Reference Include="System.Xml"/>
+		<Reference Include="System.Xml.Linq"/>
+	</ItemGroup>
+	<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets"/>
+	<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets"/>
+	<ProjectExtensions>
+		<VisualStudio>
+			<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
+				<WebProjectProperties>
+					<UseIIS>False</UseIIS>
+					<AutoAssignPort>True</AutoAssignPort>
+					<DevelopmentServerPort>10092</DevelopmentServerPort>
+					<DevelopmentServerVPath>/</DevelopmentServerVPath>
+					<IISUrl>
+					</IISUrl>
+					<NTLMAuthentication>False</NTLMAuthentication>
+					<UseCustomServer>False</UseCustomServer>
+					<CustomServerUrl>
+					</CustomServerUrl>
+					<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
+				</WebProjectProperties>
+			</FlavorProperties>
+		</VisualStudio>
+	</ProjectExtensions>
+</Project>
diff --git a/JSLib/JSLib.sln b/JSLib/JSLib.sln
new file mode 100644
index 0000000..b87a0df
--- /dev/null
+++ b/JSLib/JSLib.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JSLib", "JSLib.csproj", "{73ADF1A7-613B-4679-885B-2AE4AFAA9A6A}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{73ADF1A7-613B-4679-885B-2AE4AFAA9A6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{73ADF1A7-613B-4679-885B-2AE4AFAA9A6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{73ADF1A7-613B-4679-885B-2AE4AFAA9A6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{73ADF1A7-613B-4679-885B-2AE4AFAA9A6A}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
\ No newline at end of file
diff --git a/JSLib/Web.config b/JSLib/Web.config
new file mode 100644
index 0000000..142a457
--- /dev/null
+++ b/JSLib/Web.config
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <system.web>
+    <compilation debug="true" targetFramework="4.0">
+      <assemblies>
+        <add assembly="System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
+        <add assembly="System.Data.DataSetExtensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
+        <add assembly="System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
+        <add assembly="System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
+        <add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
+        <add assembly="System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
+        <add assembly="System.ServiceModel.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
+        <add assembly="Microsoft.Data.OData, Version=5.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
+        <add assembly="Microsoft.Data.Services, Version=5.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
+        <add assembly="Microsoft.Data.Services.Client, Version=5.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
+      </assemblies>
+    </compilation>
+  </system.web>
+  <system.webServer>
+    <directoryBrowse enabled="true" />
+  </system.webServer>
+  <system.codedom>
+    <compilers>
+      <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CSharp.CSharpCodeProvider,System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+        <providerOption name="CompilerVersion" value="v4.0" />
+      </compiler>
+    </compilers>
+  </system.codedom>
+  <system.net>
+    <defaultProxy>
+      <proxy autoDetect="True" />
+    </defaultProxy>
+  </system.net>
+</configuration>
\ No newline at end of file
diff --git a/JSLib/src/cache-source.js b/JSLib/src/cache-source.js
new file mode 100644
index 0000000..3616b84
--- /dev/null
+++ b/JSLib/src/cache-source.js
@@ -0,0 +1,183 @@
+/// <reference path="odata-utils.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// cache-source.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    var parseInt10 = datajs.parseInt10;
+    var normalizeURICase = datajs.normalizeURICase;
+
+    // CONTENT START
+
+    var appendQueryOption = function (uri, queryOption) {
+        /// <summary>Appends the specified escaped query option to the specified URI.</summary>
+        /// <param name="uri" type="String">URI to append option to.</param>
+        /// <param name="queryOption" type="String">Escaped query option to append.</param>
+        var separator = (uri.indexOf("?") >= 0) ? "&" : "?";
+        return uri + separator + queryOption;
+    };
+
+    var appendSegment = function (uri, segment) {
+        /// <summary>Appends the specified segment to the given URI.</summary>
+        /// <param name="uri" type="String">URI to append a segment to.</param>
+        /// <param name="segment" type="String">Segment to append.</param>
+        /// <returns type="String">The original URI with a new segment appended.</returns>
+
+        var index = uri.indexOf("?");
+        var queryPortion = "";
+        if (index >= 0) {
+            queryPortion = uri.substr(index);
+            uri = uri.substr(0, index);
+        }
+
+        if (uri[uri.length - 1] !== "/") {
+            uri += "/";
+        }
+        return uri + segment + queryPortion;
+    };
+
+    var buildODataRequest = function (uri, options) {
+        /// <summary>Builds a request object to GET the specified URI.</summary>
+        /// <param name="uri" type="String">URI for request.</param>
+        /// <param name="options" type="Object">Additional options.</param>
+
+        return {
+            method: "GET",
+            requestUri: uri,
+            user: options.user,
+            password: options.password,
+            enableJsonpCallback: options.enableJsonpCallback,
+            callbackParameterName: options.callbackParameterName,
+            formatQueryString: options.formatQueryString
+        };
+    };
+
+    var findQueryOptionStart = function (uri, name) {
+        /// <summary>Finds the index where the value of a query option starts.</summary>
+        /// <param name="uri" type="String">URI to search in.</param>
+        /// <param name="name" type="String">Name to look for.</param>
+        /// <returns type="Number">The index where the query option starts.</returns>
+
+        var result = -1;
+        var queryIndex = uri.indexOf("?");
+        if (queryIndex !== -1) {
+            var start = uri.indexOf("?" + name + "=", queryIndex);
+            if (start === -1) {
+                start = uri.indexOf("&" + name + "=", queryIndex);
+            }
+            if (start !== -1) {
+                result = start + name.length + 2;
+            }
+        }
+        return result;
+    };
+
+    var queryForData = function (uri, options, success, error) {
+        /// <summary>Gets data from an OData service.</summary>
+        /// <param name="uri" type="String">URI to the OData service.</param>
+        /// <param name="options" type="Object">Object with additional well-known request options.</param>
+        /// <param name="success" type="Function">Success callback.</param>
+        /// <param name="error" type="Function">Error callback.</param>
+        /// <returns type="Object">Object with an abort method.</returns>
+
+        var request = queryForDataInternal(uri, options, [], success, error);
+        return request;
+    };
+
+    var queryForDataInternal = function (uri, options, data, success, error) {
+        /// <summary>Gets data from an OData service taking into consideration server side paging.</summary>
+        /// <param name="uri" type="String">URI to the OData service.</param>
+        /// <param name="options" type="Object">Object with additional well-known request options.</param>
+        /// <param name="data" type="Array">Array that stores the data provided by the OData service.</param>
+        /// <param name="success" type="Function">Success callback.</param>
+        /// <param name="error" type="Function">Error callback.</param>
+        /// <returns type="Object">Object with an abort method.</returns>
+
+        var request = buildODataRequest(uri, options);
+        var currentRequest = odata.request(request, function (newData) {
+            var next = newData.__next;
+            var results = newData.results;
+
+            data = data.concat(results);
+
+            if (next) {
+                currentRequest = queryForDataInternal(next, options, data, success, error);
+            } else {
+                success(data);
+            }
+        }, error, undefined, options.httpClient, options.metadata);
+
+        return {
+            abort: function () {
+                currentRequest.abort();
+            }
+        };
+    };
+
+    var ODataCacheSource = function (options) {
+        /// <summary>Creates a data cache source object for requesting data from an OData service.</summary>
+        /// <param name="options">Options for the cache data source.</param>
+        /// <returns type="ODataCacheSource">A new data cache source instance.</returns>
+
+        var that = this;
+        var uri = options.source;
+        
+        that.identifier = normalizeURICase(encodeURI(decodeURI(uri)));
+        that.options = options;
+
+        that.count = function (success, error) {
+            /// <summary>Gets the number of items in the collection.</summary>
+            /// <param name="success" type="Function">Success callback with the item count.</param>
+            /// <param name="error" type="Function">Error callback.</param>
+            /// <returns type="Object">Request object with an abort method./<param>
+
+            var options = that.options;
+            return odata.request(
+                buildODataRequest(appendSegment(uri, "$count"), options),
+                function (data) {
+                    var count = parseInt10(data.toString());
+                    if (isNaN(count)) {
+                        error({ message: "Count is NaN", count: count });
+                    } else {
+                        success(count);
+                    }
+                }, error, undefined, options.httpClient, options.metadata);
+        };
+
+        that.read = function (index, count, success, error) {
+            /// <summary>Gets a number of consecutive items from the collection.</summary>
+            /// <param name="index" type="Number">Zero-based index of the items to retrieve.</param>
+            /// <param name="count" type="Number">Number of items to retrieve.</param>
+            /// <param name="success" type="Function">Success callback with the requested items.</param>
+            /// <param name="error" type="Function">Error callback.</param>
+            /// <returns type="Object">Request object with an abort method./<param>
+
+            var queryOptions = "$skip=" + index + "&$top=" + count;
+            return queryForData(appendQueryOption(uri, queryOptions), that.options, success, error);
+        };
+
+        return that;
+    };
+
+    // DATAJS INTERNAL START
+    window.datajs.ODataCacheSource = ODataCacheSource;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/cache.js b/JSLib/src/cache.js
new file mode 100644
index 0000000..b3a3a01
--- /dev/null
+++ b/JSLib/src/cache.js
@@ -0,0 +1,1349 @@
+/// <reference path="odata-utils.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// cache.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+
+    var assigned = datajs.assigned;
+    var delay = datajs.delay;
+    var extend = datajs.extend;
+    var djsassert = datajs.djsassert;
+    var isArray = datajs.isArray;
+    var normalizeURI = datajs.normalizeURI;
+    var parseInt10 = datajs.parseInt10;
+    var undefinedDefault = datajs.undefinedDefault;
+
+    var createDeferred = datajs.createDeferred;
+    var DjsDeferred = datajs.DjsDeferred;
+    var ODataCacheSource = datajs.ODataCacheSource;
+
+    // CONTENT START
+
+    var appendPage = function (operation, page) {
+        /// <summary>Appends a page's data to the operation data.</summary>
+        /// <param name="operation" type="Object">Operation with (i)ndex, (c)ount and (d)ata.</param>
+        /// <param name="page" type="Object">Page with (i)ndex, (c)ount and (d)ata.</param>
+
+        var intersection = intersectRanges(operation, page);
+        if (intersection) {
+            var start = intersection.i - page.i;
+            var end = start + (operation.c - operation.d.length);
+            operation.d = operation.d.concat(page.d.slice(start, end));
+        }
+    };
+
+    var intersectRanges = function (x, y) {
+        /// <summary>Returns the {(i)ndex, (c)ount} range for the intersection of x and y.</summary>
+        /// <param name="x" type="Object">Range with (i)ndex and (c)ount members.</param>
+        /// <param name="y" type="Object">Range with (i)ndex and (c)ount members.</param>
+        /// <returns type="Object">The intersection (i)ndex and (c)ount; undefined if there is no intersection.</returns>
+
+        var xLast = x.i + x.c;
+        var yLast = y.i + y.c;
+        var resultIndex = (x.i > y.i) ? x.i : y.i;
+        var resultLast = (xLast < yLast) ? xLast : yLast;
+        var result;
+        if (resultLast >= resultIndex) {
+            result = { i: resultIndex, c: resultLast - resultIndex };
+        }
+
+        return result;
+    };
+
+    var checkZeroGreater = function (val, name) {
+        /// <summary>Checks whether val is a defined number with value zero or greater.</summary>
+        /// <param name="val" type="Number">Value to check.</param>
+        /// <param name="name" type="String">Parameter name to use in exception.</param>
+
+        if (val === undefined || typeof val !== "number") {
+            throw { message: "'" + name + "' must be a number." };
+        }
+
+        if (isNaN(val) || val < 0 || !isFinite(val)) {
+            throw { message: "'" + name + "' must be greater than or equal to zero." };
+        }
+    };
+
+    var checkUndefinedGreaterThanZero = function (val, name) {
+        /// <summary>Checks whether val is undefined or a number with value greater than zero.</summary>
+        /// <param name="val" type="Number">Value to check.</param>
+        /// <param name="name" type="String">Parameter name to use in exception.</param>
+
+        if (val !== undefined) {
+            if (typeof val !== "number") {
+                throw { message: "'" + name + "' must be a number." };
+            }
+
+            if (isNaN(val) || val <= 0 || !isFinite(val)) {
+                throw { message: "'" + name + "' must be greater than zero." };
+            }
+        }
+    };
+
+    var checkUndefinedOrNumber = function (val, name) {
+        /// <summary>Checks whether val is undefined or a number</summary>
+        /// <param name="val" type="Number">Value to check.</param>
+        /// <param name="name" type="String">Parameter name to use in exception.</param>
+        if (val !== undefined && (typeof val !== "number" || isNaN(val) || !isFinite(val))) {
+            throw { message: "'" + name + "' must be a number." };
+        }
+    };
+
+    var removeFromArray = function (arr, item) {
+        /// <summary>Performs a linear search on the specified array and removes the first instance of 'item'.</summary>
+        /// <param name="arr" type="Array">Array to search.</param>
+        /// <param name="item">Item being sought.</param>
+        /// <returns type="Boolean">Whether the item was removed.</returns>
+
+        var i, len;
+        for (i = 0, len = arr.length; i < len; i++) {
+            if (arr[i] === item) {
+                arr.splice(i, 1);
+                return true;
+            }
+        }
+
+        return false;
+    };
+
+    var estimateSize = function (obj) {
+        /// <summary>Estimates the size of an object in bytes.</summary>
+        /// <param name="obj" type="Object">Object to determine the size of.</param>
+        /// <returns type="Integer">Estimated size of the object in bytes.</returns>
+        var size = 0;
+        var type = typeof obj;
+
+        if (type === "object" && obj) {
+            for (var name in obj) {
+                size += name.length * 2 + estimateSize(obj[name]);
+            }
+        } else if (type === "string") {
+            size = obj.length * 2;
+        } else {
+            size = 8;
+        }
+        return size;
+    };
+
+    var snapToPageBoundaries = function (lowIndex, highIndex, pageSize) {
+        /// <summary>Snaps low and high indices into page sizes and returns a range.</summary>
+        /// <param name="lowIndex" type="Number">Low index to snap to a lower value.</param>
+        /// <param name="highIndex" type="Number">High index to snap to a higher value.</param>
+        /// <param name="pageSize" type="Number">Page size to snap to.</param>
+        /// <returns type="Object">A range with (i)ndex and (c)ount of elements.</returns>
+
+        lowIndex = Math.floor(lowIndex / pageSize) * pageSize;
+        highIndex = Math.ceil((highIndex + 1) / pageSize) * pageSize;
+        return { i: lowIndex, c: highIndex - lowIndex };
+    };
+
+    // The DataCache is implemented using state machines.  The following constants are used to properly
+    // identify and label the states that these machines transition to.
+
+    // DataCache state constants
+
+    var CACHE_STATE_DESTROY = "destroy";
+    var CACHE_STATE_IDLE = "idle";
+    var CACHE_STATE_INIT = "init";
+    var CACHE_STATE_READ = "read";
+    var CACHE_STATE_PREFETCH = "prefetch";
+    var CACHE_STATE_WRITE = "write";
+
+    // DataCacheOperation state machine states.
+    // Transitions on operations also depend on the cache current of the cache.
+
+    var OPERATION_STATE_CANCEL = "cancel";
+    var OPERATION_STATE_END = "end";
+    var OPERATION_STATE_ERROR = "error";
+    var OPERATION_STATE_START = "start";
+    var OPERATION_STATE_WAIT = "wait";
+
+    // Destroy state machine states
+
+    var DESTROY_STATE_CLEAR = "clear";
+
+    // Read / Prefetch state machine states
+
+    var READ_STATE_DONE = "done";
+    var READ_STATE_LOCAL = "local";
+    var READ_STATE_SAVE = "save";
+    var READ_STATE_SOURCE = "source";
+
+    var DataCacheOperation = function (stateMachine, promise, isCancelable, index, count, data, pending) {
+        /// <summary>Creates a new operation object.</summary>
+        /// <param name="stateMachine" type="Function">State machine that describes the specific behavior of the operation.</param>
+        /// <param name="promise" type ="DjsDeferred">Promise for requested values.</param>
+        /// <param name="isCancelable" type ="Boolean">Whether this operation can be canceled or not.</param>
+        /// <param name="index" type="Number">Index of first item requested.</param>
+        /// <param name="count" type="Number">Count of items requested.</param>
+        /// <param name="data" type="Array">Array with the items requested by the operation.</param>
+        /// <param name="pending" type="Number">Total number of pending prefetch records.</param>
+        /// <returns type="DataCacheOperation">A new data cache operation instance.</returns>
+
+        /// <field name="p" type="DjsDeferred">Promise for requested values.</field>
+        /// <field name="i" type="Number">Index of first item requested.</field>
+        /// <field name="c" type="Number">Count of items requested.</field>
+        /// <field name="d" type="Array">Array with the items requested by the operation.</field>
+        /// <field name="s" type="Array">Current state of the operation.</field>
+        /// <field name="canceled" type="Boolean">Whether the operation has been canceled.</field>
+        /// <field name="pending" type="Number">Total number of pending prefetch records.</field>
+        /// <field name="oncomplete" type="Function">Callback executed when the operation reaches the end state.</field>
+
+        var stateData;
+        var cacheState;
+        var that = this;
+
+        that.p = promise;
+        that.i = index;
+        that.c = count;
+        that.d = data;
+        that.s = OPERATION_STATE_START;
+
+        that.canceled = false;
+        that.pending = pending;
+        that.oncomplete = null;
+
+        that.cancel = function () {
+            /// <summary>Transitions this operation to the cancel state and sets the canceled flag to true.</summary>
+            /// <remarks>The function is a no-op if the operation is non-cancelable.</summary>
+
+            if (!isCancelable) {
+                return;
+            }
+
+            var state = that.s;
+            if (state !== OPERATION_STATE_ERROR && state !== OPERATION_STATE_END && state !== OPERATION_STATE_CANCEL) {
+                that.canceled = true;
+                transition(OPERATION_STATE_CANCEL, stateData);
+            }
+        };
+
+        that.complete = function () {
+            /// <summary>Transitions this operation to the end state.</summary>
+
+            djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.complete() - operation is in the end state", that);
+            transition(OPERATION_STATE_END, stateData);
+        };
+
+        that.error = function (err) {
+            /// <summary>Transitions this operation to the error state.</summary>
+            if (!that.canceled) {
+                djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.error() - operation is in the end state", that);
+                djsassert(that.s !== OPERATION_STATE_ERROR, "DataCacheOperation.error() - operation is in the error state", that);
+                transition(OPERATION_STATE_ERROR, err);
+            }
+        };
+
+        that.run = function (state) {
+            /// <summary>Executes the operation's current state in the context of a new cache state.</summary>
+            /// <param name="state" type="Object">New cache state.</param>
+
+            cacheState = state;
+            that.transition(that.s, stateData);
+        };
+
+        that.wait = function (data) {
+            /// <summary>Transitions this operation to the wait state.</summary>
+
+            djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.wait() - operation is in the end state", that);
+            transition(OPERATION_STATE_WAIT, data);
+        };
+
+        var operationStateMachine = function (opTargetState, cacheState, data) {
+            /// <summary>State machine that describes all operations common behavior.</summary>
+            /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
+            /// <param name="cacheState" type="Object">Current cache state.</param>
+            /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
+
+            switch (opTargetState) {
+                case OPERATION_STATE_START:
+                    // Initial state of the operation. The operation will remain in this state until the cache has been fully initialized.
+                    if (cacheState !== CACHE_STATE_INIT) {
+                        stateMachine(that, opTargetState, cacheState, data);
+                    }
+                    break;
+
+                case OPERATION_STATE_WAIT:
+                    // Wait state indicating that the operation is active but waiting for an asynchronous operation to complete.
+                    stateMachine(that, opTargetState, cacheState, data);
+                    break;
+
+                case OPERATION_STATE_CANCEL:
+                    // Cancel state.
+                    stateMachine(that, opTargetState, cacheState, data);
+                    that.fireCanceled();
+                    transition(OPERATION_STATE_END);
+                    break;
+
+                case OPERATION_STATE_ERROR:
+                    // Error state. Data is expected to be an object detailing the error condition.
+                    stateMachine(that, opTargetState, cacheState, data);
+                    that.canceled = true;
+                    that.fireRejected(data);
+                    transition(OPERATION_STATE_END);
+                    break;
+
+                case OPERATION_STATE_END:
+                    // Final state of the operation.
+                    if (that.oncomplete) {
+                        that.oncomplete(that);
+                    }
+                    if (!that.canceled) {
+                        that.fireResolved();
+                    }
+                    stateMachine(that, opTargetState, cacheState, data);
+                    break;
+
+                default:
+                    // Any other state is passed down to the state machine describing the operation's specific behavior.
+                    // DATAJS INTERNAL START 
+                    if (true) {
+                        // Check that the state machine actually handled the sate.
+                        var handled = stateMachine(that, opTargetState, cacheState, data);
+                        djsassert(handled, "Bad operation state: " + opTargetState + " cacheState: " + cacheState, this);
+                    } else {
+                        // DATAJS INTERNAL END 
+                        stateMachine(that, opTargetState, cacheState, data);
+                        // DATAJS INTERNAL START
+                    }
+                    // DATAJS INTERNAL END
+                    break;
+            }
+        };
+
+        var transition = function (state, data) {
+            /// <summary>Transitions this operation to a new state.</summary>
+            /// <param name="state" type="Object">State to transition the operation to.</param>
+            /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
+
+            that.s = state;
+            stateData = data;
+            operationStateMachine(state, cacheState, data);
+        };
+
+        that.transition = transition;
+
+        return that;
+    };
+
+    DataCacheOperation.prototype.fireResolved = function () {
+        /// <summary>Fires a resolved notification as necessary.</summary>
+
+        // Fire the resolve just once.
+        var p = this.p;
+        if (p) {
+            this.p = null;
+            p.resolve(this.d);
+        }
+    };
+
+    DataCacheOperation.prototype.fireRejected = function (reason) {
+        /// <summary>Fires a rejected notification as necessary.</summary>
+
+        // Fire the rejection just once.
+        var p = this.p;
+        if (p) {
+            this.p = null;
+            p.reject(reason);
+        }
+    };
+
+    DataCacheOperation.prototype.fireCanceled = function () {
+        /// <summary>Fires a canceled notification as necessary.</summary>
+
+        this.fireRejected({ canceled: true, message: "Operation canceled" });
+    };
+
+
+    var DataCache = function (options) {
+        /// <summary>Creates a data cache for a collection that is efficiently loaded on-demand.</summary>
+        /// <param name="options">
+        /// Options for the data cache, including name, source, pageSize,
+        /// prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
+        /// </param>
+        /// <returns type="DataCache">A new data cache instance.</returns>
+
+        var state = CACHE_STATE_INIT;
+        var stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
+
+        var clearOperations = [];
+        var readOperations = [];
+        var prefetchOperations = [];
+
+        var actualCacheSize = 0;                                             // Actual cache size in bytes.
+        var allDataLocal = false;                                            // Whether all data is local.
+        var cacheSize = undefinedDefault(options.cacheSize, 1048576);        // Requested cache size in bytes, default 1 MB.
+        var collectionCount = 0;                                             // Number of elements in the server collection.
+        var highestSavedPage = 0;                                            // Highest index of all the saved pages.
+        var highestSavedPageSize = 0;                                        // Item count of the saved page with the highest index.
+        var overflowed = cacheSize === 0;                                    // If the cache has overflowed (actualCacheSize > cacheSize or cacheSize == 0);
+        var pageSize = undefinedDefault(options.pageSize, 50);               // Number of elements to store per page.
+        var prefetchSize = undefinedDefault(options.prefetchSize, pageSize); // Number of elements to prefetch from the source when the cache is idling.
+        var version = "1.0";
+        var cacheFailure;
+
+        var pendingOperations = 0;
+
+        var source = options.source;
+        if (typeof source === "string") {
+            // Create a new cache source.
+            source = new ODataCacheSource(options);
+        }
+        source.options = options;
+
+        // Create a cache local store.
+        var store = datajs.createStore(options.name, options.mechanism);
+
+        var that = this;
+
+        that.onidle = options.idle;
+        that.stats = stats;
+
+        that.count = function () {
+            /// <summary>Counts the number of items in the collection.</summary>
+            /// <returns type="Object">A promise with the number of items.</returns>
+
+            if (cacheFailure) {
+                throw cacheFailure;
+            }
+
+            var deferred = createDeferred();
+            var canceled = false;
+
+            if (allDataLocal) {
+                delay(function () {
+                    deferred.resolve(collectionCount);
+                });
+
+                return deferred.promise();
+            }
+
+            // TODO: Consider returning the local data count instead once allDataLocal flag is set to true.
+            var request = source.count(function (count) {
+                request = null;
+                stats.counts++;
+                deferred.resolve(count);
+            }, function (err) {
+                request = null;
+                deferred.reject(extend(err, { canceled: canceled }));
+            });
+
+            return extend(deferred.promise(), {
+                cancel: function () {
+                    /// <summary>Aborts the count operation.</summary>
+                    if (request) {
+                        canceled = true;
+                        request.abort();
+                        request = null;
+                    }
+                }
+            });
+        };
+
+        that.clear = function () {
+            /// <summary>Cancels all running operations and clears all local data associated with this cache.</summary>
+            /// <remarks>
+            /// New read requests made while a clear operation is in progress will not be canceled.
+            /// Instead they will be queued for execution once the operation is completed.
+            /// </remarks>
+            /// <returns type="Object">A promise that has no value and can't be canceled.</returns>
+
+            if (cacheFailure) {
+                throw cacheFailure;
+            }
+
+            if (clearOperations.length === 0) {
+                var deferred = createDeferred();
+                var op = new DataCacheOperation(destroyStateMachine, deferred, false);
+                queueAndStart(op, clearOperations);
+                return deferred.promise();
+            }
+            return clearOperations[0].p;
+        };
+
+        that.filterForward = function (index, count, predicate) {
+            /// <summary>Filters the cache data based a predicate.</summary>
+            /// <param name="index" type="Number">The index of the item to start filtering forward from.</param>
+            /// <param name="count" type="Number">Maximum number of items to include in the result.</param>
+            /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param>
+            /// <remarks>
+            /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
+            /// </remarks>
+            /// <returns type="DjsDeferred">A promise for an array of results.</returns>
+            return filter(index, count, predicate, false);
+        };
+
+        that.filterBack = function (index, count, predicate) {
+            /// <summary>Filters the cache data based a predicate.</summary>
+            /// <param name="index" type="Number">The index of the item to start filtering backward from.</param>
+            /// <param name="count" type="Number">Maximum number of items to include in the result.</param>
+            /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param>
+            /// <remarks>
+            /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
+            /// </remarks>
+            /// <returns type="DjsDeferred">A promise for an array of results.</returns>
+            return filter(index, count, predicate, true);
+        };
+
+        that.readRange = function (index, count) {
+            /// <summary>Reads a range of adjacent records.</summary>
+            /// <param name="index" type="Number">Zero-based index of record range to read.</param>
+            /// <param name="count" type="Number">Number of records in the range.</param>
+            /// <remarks>
+            /// New read requests made while a clear operation is in progress will not be canceled.
+            /// Instead they will be queued for execution once the operation is completed.
+            /// </remarks>
+            /// <returns type="DjsDeferred">
+            /// A promise for an array of records; less records may be returned if the
+            /// end of the collection is found.
+            /// </returns>
+
+            checkZeroGreater(index, "index");
+            checkZeroGreater(count, "count");
+
+            if (cacheFailure) {
+                throw cacheFailure;
+            }
+
+            var deferred = createDeferred();
+
+            // Merging read operations would be a nice optimization here.
+            var op = new DataCacheOperation(readStateMachine, deferred, true, index, count, [], 0);
+            queueAndStart(op, readOperations);
+
+            return extend(deferred.promise(), {
+                cancel: function () {
+                    /// <summary>Aborts the readRange operation.</summary>
+                    op.cancel();
+                }
+            });
+        };
+
+        that.ToObservable = that.toObservable = function () {
+            /// <summary>Creates an Observable object that enumerates all the cache contents.</summary>
+            /// <returns>A new Observable object that enumerates all the cache contents.</returns>
+            if (!window.Rx || !window.Rx.Observable) {
+                throw { message: "Rx library not available - include rx.js" };
+            }
+
+            if (cacheFailure) {
+                throw cacheFailure;
+            }
+
+            return window.Rx.Observable.CreateWithDisposable(function (obs) {
+                var disposed = false;
+                var index = 0;
+
+                var errorCallback = function (error) {
+                    if (!disposed) {
+                        obs.OnError(error);
+                    }
+                };
+
+                var successCallback = function (data) {
+                    if (!disposed) {
+                        var i, len;
+                        for (i = 0, len = data.length; i < len; i++) {
+                            // The wrapper automatically checks for Dispose
+                            // on the observer, so we don't need to check it here.
+                            obs.OnNext(data[i]);
+                        }
+
+                        if (data.length < pageSize) {
+                            obs.OnCompleted();
+                        } else {
+                            index += pageSize;
+                            that.readRange(index, pageSize).then(successCallback, errorCallback);
+                        }
+                    }
+                };
+
+                that.readRange(index, pageSize).then(successCallback, errorCallback);
+
+                return { Dispose: function () { disposed = true; } };
+            });
+        };
+
+        var cacheFailureCallback = function (message) {
+            /// <summary>Creates a function that handles a callback by setting the cache into failure mode.</summary>
+            /// <param name="message" type="String">Message text.</param>
+            /// <returns type="Function">Function to use as error callback.</returns>
+            /// <remarks>
+            /// This function will specifically handle problems with critical store resources
+            /// during cache initialization.
+            /// </remarks>
+
+            return function (error) {
+                cacheFailure = { message: message, error: error };
+
+                // Destroy any pending clear or read operations.
+                // At this point there should be no prefetch operations.
+                // Count operations will go through but are benign because they
+                // won't interact with the store.
+                djsassert(prefetchOperations.length === 0, "prefetchOperations.length === 0");
+                var i, len;
+                for (i = 0, len = readOperations.length; i < len; i++) {
+                    readOperations[i].fireRejected(cacheFailure);
+                }
+                for (i = 0, len = clearOperations.length; i < len; i++) {
+                    clearOperations[i].fireRejected(cacheFailure);
+                }
+
+                // Null out the operation arrays.
+                readOperations = clearOperations = null;
+            };
+        };
+
+        var changeState = function (newState) {
+            /// <summary>Updates the cache's state and signals all pending operations of the change.</summary>
+            /// <param name="newState" type="Object">New cache state.</param>
+            /// <remarks>This method is a no-op if the cache's current state and the new state are the same.</remarks>
+
+            if (newState !== state) {
+                state = newState;
+                var operations = clearOperations.concat(readOperations, prefetchOperations);
+                var i, len;
+                for (i = 0, len = operations.length; i < len; i++) {
+                    operations[i].run(state);
+                }
+            }
+        };
+
+        var clearStore = function () {
+            /// <summary>Removes all the data stored in the cache.</summary>
+            /// <returns type="DjsDeferred">A promise with no value.</returns>
+            djsassert(state === CACHE_STATE_DESTROY || state === CACHE_STATE_INIT, "DataCache.clearStore() - cache is not on the destroy or initialize state, current sate = " + state);
+
+            var deferred = new DjsDeferred();
+            store.clear(function () {
+
+                // Reset the cache settings.
+                actualCacheSize = 0;
+                allDataLocal = false;
+                collectionCount = 0;
+                highestSavedPage = 0;
+                highestSavedPageSize = 0;
+                overflowed = cacheSize === 0;
+
+                // version is not reset, in case there is other state in eg V1.1 that is still around.
+
+                // Reset the cache stats.
+                stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
+                that.stats = stats;
+
+                store.close();
+                deferred.resolve();
+            }, function (err) {
+                deferred.reject(err);
+            });
+            return deferred;
+        };
+
+        var dequeueOperation = function (operation) {
+            /// <summary>Removes an operation from the caches queues and changes the cache state to idle.</summary>
+            /// <param name="operation" type="DataCacheOperation">Operation to dequeue.</param>
+            /// <remarks>This method is used as a handler for the operation's oncomplete event.</remarks>
+
+            var removed = removeFromArray(clearOperations, operation);
+            if (!removed) {
+                removed = removeFromArray(readOperations, operation);
+                if (!removed) {
+                    removeFromArray(prefetchOperations, operation);
+                }
+            }
+
+            pendingOperations--;
+            changeState(CACHE_STATE_IDLE);
+        };
+
+        var fetchPage = function (start) {
+            /// <summary>Requests data from the cache source.</summary>
+            /// <param name="start" type="Number">Zero-based index of items to request.</param>
+            /// <returns type="DjsDeferred">A promise for a page object with (i)ndex, (c)ount, (d)ata.</returns>
+
+            djsassert(state !== CACHE_STATE_DESTROY, "DataCache.fetchPage() - cache is on the destroy state");
+            djsassert(state !== CACHE_STATE_IDLE, "DataCache.fetchPage() - cache is on the idle state");
+
+            var deferred = new DjsDeferred();
+            var canceled = false;
+
+            var request = source.read(start, pageSize, function (data) {
+                var page = { i: start, c: data.length, d: data };
+                deferred.resolve(page);
+            }, function (err) {
+                deferred.reject(err);
+            });
+
+            return extend(deferred, {
+                cancel: function () {
+                    if (request) {
+                        request.abort();
+                        canceled = true;
+                        request = null;
+                    }
+                }
+            });
+        };
+
+        var filter = function (index, count, predicate, backwards) {
+            /// <summary>Filters the cache data based a predicate.</summary>
+            /// <param name="index" type="Number">The index of the item to start filtering from.</param>
+            /// <param name="count" type="Number">Maximum number of items to include in the result.</param>
+            /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param>
+            /// <param name="backwards" type="Boolean">True if the filtering should move backward from the specified index, falsey otherwise.</param>
+            /// <remarks>
+            /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
+            /// </remarks>
+            /// <returns type="DjsDeferred">A promise for an array of results.</returns>
+            index = parseInt10(index);
+            count = parseInt10(count);
+
+            if (isNaN(index)) {
+                throw { message: "'index' must be a valid number.", index: index };
+            }
+            if (isNaN(count)) {
+                throw { message: "'count' must be a valid number.", count: count };
+            }
+
+            if (cacheFailure) {
+                throw cacheFailure;
+            }
+
+            index = Math.max(index, 0);
+
+            var deferred = createDeferred();
+            var arr = [];
+            var canceled = false;
+            var pendingReadRange = null;
+
+            var readMore = function (readIndex, readCount) {
+                if (!canceled) {
+                    if (count >= 0 && arr.length >= count) {
+                        deferred.resolve(arr);
+                    } else {
+                        pendingReadRange = that.readRange(readIndex, readCount).then(function (data) {
+                            for (var i = 0, length = data.length; i < length && (count < 0 || arr.length < count); i++) {
+                                var dataIndex = backwards ? length - i - 1 : i;
+                                var item = data[dataIndex];
+                                if (predicate(item)) {
+                                    var element = {
+                                        index: readIndex + dataIndex,
+                                        item: item
+                                    };
+
+                                    backwards ? arr.unshift(element) : arr.push(element);
+                                }
+                            }
+
+                            // Have we reached the end of the collection?
+                            if ((!backwards && data.length < readCount) || (backwards && readIndex <= 0)) {
+                                deferred.resolve(arr);
+                            } else {
+                                var nextIndex = backwards ? Math.max(readIndex - pageSize, 0) : readIndex + readCount;
+                                readMore(nextIndex, pageSize);
+                            }
+                        }, function (err) {
+                            deferred.reject(err);
+                        });
+                    }
+                }
+            };
+
+            // Initially, we read from the given starting index to the next/previous page boundary
+            var initialPage = snapToPageBoundaries(index, index, pageSize);
+            var initialIndex = backwards ? initialPage.i : index;
+            var initialCount = backwards ? index - initialPage.i + 1 : initialPage.i + initialPage.c - index;
+            readMore(initialIndex, initialCount);
+
+            return extend(deferred.promise(), {
+                cancel: function () {
+                    /// <summary>Aborts the filter operation</summary>
+                    if (pendingReadRange) {
+                        pendingReadRange.cancel();
+                    }
+                    canceled = true;
+                }
+            });
+        };
+
+        var fireOnIdle = function () {
+            /// <summary>Fires an onidle event if any functions are assigned.</summary>
+
+            if (that.onidle && pendingOperations === 0) {
+                that.onidle();
+            }
+        };
+
+        var prefetch = function (start) {
+            /// <summary>Creates and starts a new prefetch operation.</summary>
+            /// <param name="start" type="Number">Zero-based index of the items to prefetch.</param>
+            /// <remarks>
+            /// This method is a no-op if any of the following conditions is true:
+            ///     1.- prefetchSize is 0
+            ///     2.- All data has been read and stored locally in the cache.
+            ///     3.- There is already an all data prefetch operation queued.
+            ///     4.- The cache has run out of available space (overflowed).
+            /// <remarks>
+
+            if (allDataLocal || prefetchSize === 0 || overflowed) {
+                return;
+            }
+
+            djsassert(state === CACHE_STATE_READ, "DataCache.prefetch() - cache is not on the read state, current state: " + state);
+
+            if (prefetchOperations.length === 0 || (prefetchOperations[0] && prefetchOperations[0].c !== -1)) {
+                // Merging prefetch operations would be a nice optimization here.
+                var op = new DataCacheOperation(prefetchStateMachine, null, true, start, prefetchSize, null, prefetchSize);
+                queueAndStart(op, prefetchOperations);
+            }
+        };
+
+        var queueAndStart = function (op, queue) {
+            /// <summary>Queues an operation and runs it.</summary>
+            /// <param name="op" type="DataCacheOperation">Operation to queue.</param>
+            /// <param name="queue" type="Array">Array that will store the operation.</param>
+
+            op.oncomplete = dequeueOperation;
+            queue.push(op);
+            pendingOperations++;
+            op.run(state);
+        };
+
+        var readPage = function (key) {
+            /// <summary>Requests a page from the cache local store.</summary>
+            /// <param name="key" type="Number">Zero-based index of the reuqested page.</param>
+            /// <returns type="DjsDeferred">A promise for a found flag and page object with (i)ndex, (c)ount, (d)ata, and (t)icks.</returns>
+
+            djsassert(state !== CACHE_STATE_DESTROY, "DataCache.readPage() - cache is on the destroy state");
+
+            var canceled = false;
+            var deferred = extend(new DjsDeferred(), {
+                cancel: function () {
+                    /// <summary>Aborts the readPage operation.</summary>
+                    canceled = true;
+                }
+            });
+
+            var error = storeFailureCallback(deferred, "Read page from store failure");
+
+            store.contains(key, function (contained) {
+                if (canceled) {
+                    return;
+                }
+                if (contained) {
+                    store.read(key, function (_, data) {
+                        if (!canceled) {
+                            deferred.resolve(data !== undefined, data);
+                        }
+                    }, error);
+                    return;
+                }
+                deferred.resolve(false);
+            }, error);
+            return deferred;
+        };
+
+        var savePage = function (key, page) {
+            /// <summary>Saves a page to the cache local store.</summary>
+            /// <param name="key" type="Number">Zero-based index of the requested page.</param>
+            /// <param name="page" type="Object">Object with (i)ndex, (c)ount, (d)ata, and (t)icks.</param>
+            /// <returns type="DjsDeferred">A promise with no value.</returns>
+
+            djsassert(state !== CACHE_STATE_DESTROY, "DataCache.savePage() - cache is on the destroy state");
+            djsassert(state !== CACHE_STATE_IDLE, "DataCache.savePage() - cache is on the idle state");
+
+            var canceled = false;
+
+            var deferred = extend(new DjsDeferred(), {
+                cancel: function () {
+                    /// <summary>Aborts the readPage operation.</summary>
+                    canceled = true;
+                }
+            });
+
+            var error = storeFailureCallback(deferred, "Save page to store failure");
+
+            var resolve = function () {
+                deferred.resolve(true);
+            };
+
+            if (page.c > 0) {
+                var pageBytes = estimateSize(page);
+                overflowed = cacheSize >= 0 && cacheSize < actualCacheSize + pageBytes;
+
+                if (!overflowed) {
+                    store.addOrUpdate(key, page, function () {
+                        updateSettings(page, pageBytes);
+                        saveSettings(resolve, error);
+                    }, error);
+                } else {
+                    resolve();
+                }
+            } else {
+                updateSettings(page, 0);
+                saveSettings(resolve, error);
+            }
+            return deferred;
+        };
+
+        var saveSettings = function (success, error) {
+            /// <summary>Saves the cache's current settings to the local store.</summary>
+            /// <param name="success" type="Function">Success callback.</param>
+            /// <param name="error" type="Function">Errror callback.</param>
+
+            var settings = {
+                actualCacheSize: actualCacheSize,
+                allDataLocal: allDataLocal,
+                cacheSize: cacheSize,
+                collectionCount: collectionCount,
+                highestSavedPage: highestSavedPage,
+                highestSavedPageSize: highestSavedPageSize,
+                pageSize: pageSize,
+                sourceId: source.identifier,
+                version: version
+            };
+
+            store.addOrUpdate("__settings", settings, success, error);
+        };
+
+        var storeFailureCallback = function (deferred/*, message*/) {
+            /// <summary>Creates a function that handles a store error.</summary>
+            /// <param name="deferred" type="DjsDeferred">Deferred object to resolve.</param>
+            /// <param name="message" type="String">Message text.</param>
+            /// <returns type="Function">Function to use as error callback.</returns>
+            /// <remarks>
+            /// This function will specifically handle problems when interacting with the store.
+            /// </remarks>
+
+            return function (/*error*/) {
+                // var console = window.console;
+                // if (console && console.log) {
+                //    console.log(message);
+                //    console.dir(error);
+                // }
+                deferred.resolve(false);
+            };
+        };
+
+        var updateSettings = function (page, pageBytes) {
+            /// <summary>Updates the cache's settings based on a page object.</summary>
+            /// <param name="page" type="Object">Object with (i)ndex, (c)ount, (d)ata.</param>
+            /// <param name="pageBytes" type="Number">Size of the page in bytes.</param>
+
+            var pageCount = page.c;
+            var pageIndex = page.i;
+
+            // Detect the collection size.
+            if (pageCount === 0) {
+                if (highestSavedPage === pageIndex - pageSize) {
+                    collectionCount = highestSavedPage + highestSavedPageSize;
+                }
+            } else {
+                highestSavedPage = Math.max(highestSavedPage, pageIndex);
+                if (highestSavedPage === pageIndex) {
+                    highestSavedPageSize = pageCount;
+                }
+                actualCacheSize += pageBytes;
+                if (pageCount < pageSize && !collectionCount) {
+                    collectionCount = pageIndex + pageCount;
+                }
+            }
+
+            // Detect the end of the collection.
+            if (!allDataLocal && collectionCount === highestSavedPage + highestSavedPageSize) {
+                allDataLocal = true;
+            }
+        };
+
+        var cancelStateMachine = function (operation, opTargetState, cacheState, data) {
+            /// <summary>State machine describing the behavior for cancelling a read or prefetch operation.</summary>
+            /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
+            /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
+            /// <param name="cacheState" type="Object">Current cache state.</param>
+            /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
+            /// <remarks>
+            /// This state machine contains behavior common to read and prefetch operations.
+            /// </remarks>
+
+            var canceled = operation.canceled && opTargetState !== OPERATION_STATE_END;
+            if (canceled) {
+                if (opTargetState === OPERATION_STATE_CANCEL) {
+                    // Cancel state.
+                    // Data is expected to be any pending request made to the cache.
+                    if (data && data.cancel) {
+                        data.cancel();
+                    }
+                }
+            }
+            return canceled;
+        };
+
+        var destroyStateMachine = function (operation, opTargetState, cacheState) {
+            /// <summary>State machine describing the behavior of a clear operation.</summary>
+            /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
+            /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
+            /// <param name="cacheState" type="Object">Current cache state.</param>
+            /// <remarks>
+            /// Clear operations have the highest priority and can't be interrupted by other operations; however,
+            /// they will preempt any other operation currently executing.
+            /// </remarks>
+
+            var transition = operation.transition;
+
+            // Signal the cache that a clear operation is running.
+            if (cacheState !== CACHE_STATE_DESTROY) {
+                changeState(CACHE_STATE_DESTROY);
+                return true;
+            }
+
+            switch (opTargetState) {
+                case OPERATION_STATE_START:
+                    // Initial state of the operation.
+                    transition(DESTROY_STATE_CLEAR);
+                    break;
+
+                case OPERATION_STATE_END:
+                    // State that signals the operation is done.
+                    fireOnIdle();
+                    break;
+
+                case DESTROY_STATE_CLEAR:
+                    // State that clears all the local data of the cache.
+                    clearStore().then(function () {
+                        // Terminate the operation once the local store has been cleared.
+                        operation.complete();
+                    });
+                    // Wait until the clear request completes.
+                    operation.wait();
+                    break;
+
+                default:
+                    return false;
+            }
+            return true;
+        };
+
+        var prefetchStateMachine = function (operation, opTargetState, cacheState, data) {
+            /// <summary>State machine describing the behavior of a prefetch operation.</summary>
+            /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
+            /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
+            /// <param name="cacheState" type="Object">Current cache state.</param>
+            /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
+            /// <remarks>
+            /// Prefetch operations have the lowest priority and will be interrupted by operations of
+            /// other kinds. A preempted prefetch operation will resume its execution only when the state
+            /// of the cache returns to idle.
+            ///
+            /// If a clear operation starts executing then all the prefetch operations are canceled,
+            /// even if they haven't started executing yet.
+            /// </remarks>
+
+            // Handle cancelation
+            if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
+
+                var transition = operation.transition;
+
+                // Handle preemption
+                if (cacheState !== CACHE_STATE_PREFETCH) {
+                    if (cacheState === CACHE_STATE_DESTROY) {
+                        if (opTargetState !== OPERATION_STATE_CANCEL) {
+                            operation.cancel();
+                        }
+                    } else if (cacheState === CACHE_STATE_IDLE) {
+                        // Signal the cache that a prefetch operation is running.
+                        changeState(CACHE_STATE_PREFETCH);
+                    }
+                    return true;
+                }
+
+                switch (opTargetState) {
+                    case OPERATION_STATE_START:
+                        // Initial state of the operation.
+                        if (prefetchOperations[0] === operation) {
+                            transition(READ_STATE_LOCAL, operation.i);
+                        }
+                        break;
+
+                    case READ_STATE_DONE:
+                        // State that determines if the operation can be resolved or has to
+                        // continue processing.
+                        // Data is expected to be the read page.
+                        var pending = operation.pending;
+
+                        if (pending > 0) {
+                            pending -= Math.min(pending, data.c);
+                        }
+
+                        // Are we done, or has all the data been stored?
+                        if (allDataLocal || pending === 0 || data.c < pageSize || overflowed) {
+                            operation.complete();
+                        } else {
+                            // Continue processing the operation.
+                            operation.pending = pending;
+                            transition(READ_STATE_LOCAL, data.i + pageSize);
+                        }
+                        break;
+
+                    default:
+                        return readSaveStateMachine(operation, opTargetState, cacheState, data, true);
+                }
+            }
+            return true;
+        };
+
+        var readStateMachine = function (operation, opTargetState, cacheState, data) {
+            /// <summary>State machine describing the behavior of a read operation.</summary>
+            /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
+            /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
+            /// <param name="cacheState" type="Object">Current cache state.</param>
+            /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
+            /// <remarks>
+            /// Read operations have a higher priority than prefetch operations, but lower than
+            /// clear operations. They will preempt any prefetch operation currently running
+            /// but will be interrupted by a clear operation.
+            ///
+            /// If a clear operation starts executing then all the currently running
+            /// read operations are canceled. Read operations that haven't started yet will
+            /// wait in the start state until the destory operation finishes.
+            /// </remarks>
+
+            // Handle cancelation
+            if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
+
+                var transition = operation.transition;
+
+                // Handle preemption
+                if (cacheState !== CACHE_STATE_READ && opTargetState !== OPERATION_STATE_START) {
+                    if (cacheState === CACHE_STATE_DESTROY) {
+                        if (opTargetState !== OPERATION_STATE_START) {
+                            operation.cancel();
+                        }
+                    } else if (cacheState !== CACHE_STATE_WRITE) {
+                        // Signal the cache that a read operation is running.
+                        djsassert(state == CACHE_STATE_IDLE || state === CACHE_STATE_PREFETCH, "DataCache.readStateMachine() - cache is not on the read or idle state.");
+                        changeState(CACHE_STATE_READ);
+                    }
+
+                    return true;
+                }
+
+                switch (opTargetState) {
+                    case OPERATION_STATE_START:
+                        // Initial state of the operation.
+                        // Wait until the cache is idle or prefetching.
+                        if (cacheState === CACHE_STATE_IDLE || cacheState === CACHE_STATE_PREFETCH) {
+                            // Signal the cache that a read operation is running.
+                            changeState(CACHE_STATE_READ);
+                            if (operation.c > 0) {
+                                // Snap the requested range to a page boundary.
+                                var range = snapToPageBoundaries(operation.i, operation.c, pageSize);
+                                transition(READ_STATE_LOCAL, range.i);
+                            } else {
+                                transition(READ_STATE_DONE, operation);
+                            }
+                        }
+                        break;
+
+                    case READ_STATE_DONE:
+                        // State that determines if the operation can be resolved or has to
+                        // continue processing.
+                        // Data is expected to be the read page.
+                        appendPage(operation, data);
+                        var len = operation.d.length;
+                        // Are we done?
+                        if (operation.c === len || data.c < pageSize) {
+                            // Update the stats, request for a prefetch operation.
+                            stats.cacheReads++;
+                            prefetch(data.i + data.c);
+                            // Terminate the operation.
+                            operation.complete();
+                        } else {
+                            // Continue processing the operation.
+                            transition(READ_STATE_LOCAL, data.i + pageSize);
+                        }
+                        break;
+
+                    default:
+                        return readSaveStateMachine(operation, opTargetState, cacheState, data, false);
+                }
+            }
+
+            return true;
+        };
+
+        var readSaveStateMachine = function (operation, opTargetState, cacheState, data, isPrefetch) {
+            /// <summary>State machine describing the behavior for reading and saving data into the cache.</summary>
+            /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
+            /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
+            /// <param name="cacheState" type="Object">Current cache state.</param>
+            /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
+            /// <param name="isPrefetch" type="Boolean">Flag indicating whether a read (false) or prefetch (true) operation is running.
+            /// <remarks>
+            /// This state machine contains behavior common to read and prefetch operations.
+            /// </remarks>
+
+            var error = operation.error;
+            var transition = operation.transition;
+            var wait = operation.wait;
+            var request;
+
+            switch (opTargetState) {
+                case OPERATION_STATE_END:
+                    // State that signals the operation is done.
+                    fireOnIdle();
+                    break;
+
+                case READ_STATE_LOCAL:
+                    // State that requests for a page from the local store.
+                    // Data is expected to be the index of the page to request.
+                    request = readPage(data).then(function (found, page) {
+                        // Signal the cache that a read operation is running.
+                        if (!operation.canceled) {
+                            if (found) {
+                                // The page is in the local store, check if the operation can be resolved.
+                                transition(READ_STATE_DONE, page);
+                            } else {
+                                // The page is not in the local store, request it from the source.
+                                transition(READ_STATE_SOURCE, data);
+                            }
+                        }
+                    });
+                    break;
+
+                case READ_STATE_SOURCE:
+                    // State that requests for a page from the cache source.
+                    // Data is expected to be the index of the page to request.
+                    request = fetchPage(data).then(function (page) {
+                        // Signal the cache that a read operation is running.
+                        if (!operation.canceled) {
+                            // Update the stats and save the page to the local store.
+                            if (isPrefetch) {
+                                stats.prefetches++;
+                            } else {
+                                stats.netReads++;
+                            }
+                            transition(READ_STATE_SAVE, page);
+                        }
+                    }, error);
+                    break;
+
+                case READ_STATE_SAVE:
+                    // State that saves a  page to the local store.
+                    // Data is expected to be the page to save.
+                    // Write access to the store is exclusive.
+                    if (cacheState !== CACHE_STATE_WRITE) {
+                        changeState(CACHE_STATE_WRITE);
+                        request = savePage(data.i, data).then(function (saved) {
+                            if (!operation.canceled) {
+                                if (!saved && isPrefetch) {
+                                    operation.pending = 0;
+                                }
+                                // Check if the operation can be resolved.
+                                transition(READ_STATE_DONE, data);
+                            }
+                            changeState(CACHE_STATE_IDLE);
+                        });
+                    }
+                    break;
+
+                default:
+                    // Unknown state that can't be handled by this state machine.
+                    return false;
+            }
+
+            if (request) {
+                // The operation might have been canceled between stack frames do to the async calls.
+                if (operation.canceled) {
+                    request.cancel();
+                } else if (operation.s === opTargetState) {
+                    // Wait for the request to complete.
+                    wait(request);
+                }
+            }
+
+            return true;
+        };
+
+        // Initialize the cache.
+        store.read("__settings", function (_, settings) {
+            if (assigned(settings)) {
+                var settingsVersion = settings.version;
+                if (!settingsVersion || settingsVersion.indexOf("1.") !== 0) {
+                    cacheFailureCallback("Unsupported cache store version " + settingsVersion)();
+                    return;
+                }
+
+                if (pageSize !== settings.pageSize || source.identifier !== settings.sourceId) {
+                    // The shape or the source of the data was changed so invalidate the store.
+                    clearStore().then(function () {
+                        // Signal the cache is fully initialized.
+                        changeState(CACHE_STATE_IDLE);
+                    }, cacheFailureCallback("Unable to clear store during initialization"));
+                } else {
+                    // Restore the saved settings.
+                    actualCacheSize = settings.actualCacheSize;
+                    allDataLocal = settings.allDataLocal;
+                    cacheSize = settings.cacheSize;
+                    collectionCount = settings.collectionCount;
+                    highestSavedPage = settings.highestSavedPage;
+                    highestSavedPageSize = settings.highestSavedPageSize;
+                    version = settingsVersion;
+
+                    // Signal the cache is fully initialized.
+                    changeState(CACHE_STATE_IDLE);
+                }
+            } else {
+                // This is a brand new cache.
+                saveSettings(function () {
+                    // Signal the cache is fully initialized.
+                    changeState(CACHE_STATE_IDLE);
+                }, cacheFailureCallback("Unable to write settings during initialization."));
+            }
+        }, cacheFailureCallback("Unable to read settings from store."));
+
+        return that;
+    };
+
+    datajs.createDataCache = function (options) {
+        /// <summary>Creates a data cache for a collection that is efficiently loaded on-demand.</summary>
+        /// <param name="options">
+        /// Options for the data cache, including name, source, pageSize,
+        /// prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
+        /// </param>
+        /// <returns type="DataCache">A new data cache instance.</returns>
+        checkUndefinedGreaterThanZero(options.pageSize, "pageSize");
+        checkUndefinedOrNumber(options.cacheSize, "cacheSize");
+        checkUndefinedOrNumber(options.prefetchSize, "prefetchSize");
+
+        if (!assigned(options.name)) {
+            throw { message: "Undefined or null name", options: options };
+        }
+
+        if (!assigned(options.source)) {
+            throw { message: "Undefined source", options: options };
+        }
+
+        return new DataCache(options);
+    };
+
+    // DATAJS INTERNAL START
+    window.datajs.estimateSize = estimateSize;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/datajs.js b/JSLib/src/datajs.js
new file mode 100644
index 0000000..fc5bb62
--- /dev/null
+++ b/JSLib/src/datajs.js
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// datajs.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // AMD support
+    if (typeof define === 'function' && define.amd) {
+        define('datajs', datajs);
+        define('OData', odata);
+    } else {
+        window.datajs = datajs;
+        window.OData = odata;
+    }
+
+    datajs.version = {
+        major: 1,
+        minor: 1,
+        build: 1
+    };
+
+    // INCLUDE: utils.js
+    // INCLUDE: xml.js
+
+    // INCLUDE: deferred.js
+
+    // INCLUDE: odata-utils.js
+    // INCLUDE: odata-net.js
+    // INCLUDE: odata-handler.js
+    // INCLUDE: odata-gml.js
+    // INCLUDE: odata-xml.js
+    // INCLUDE: odata-atom.js
+    // INCLUDE: odata-metadata.js
+    // INCLUDE: odata-json-light.js
+    // INCLUDE: odata-json.js
+    // INCLUDE: odata-batch.js
+    // INCLUDE: odata.js
+
+    // INCLUDE: store-dom.js
+    // INCLUDE: store-indexeddb.js
+    // INCLUDE: store-memory.js
+    // INCLUDE: store.js
+
+    // INCLUDE: cache-source.js
+    // INCLUDE: cache.js
+
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/deferred.js b/JSLib/src/deferred.js
new file mode 100644
index 0000000..7f6f6a8
--- /dev/null
+++ b/JSLib/src/deferred.js
@@ -0,0 +1,179 @@
+/// <reference path="odata-utils.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// deferred.js
+
+(function (window, undefined) {
+
+    // CONTENT START
+
+    var forwardCall = function (thisValue, name, returnValue) {
+        /// <summary>Creates a new function to forward a call.</summary>
+        /// <param name="thisValue" type="Object">Value to use as the 'this' object.</param>
+        /// <param name="name" type="String">Name of function to forward to.</param>
+        /// <param name="returnValue" type="Object">Return value for the forward call (helps keep identity when chaining calls).</param>
+        /// <returns type="Function">A new function that will forward a call.</returns>
+
+        return function () {
+            thisValue[name].apply(thisValue, arguments);
+            return returnValue;
+        };
+    };
+
+    var DjsDeferred = function () {
+        /// <summary>Initializes a new DjsDeferred object.</summary>
+        /// <remarks>
+        /// Compability Note A - Ordering of callbacks through chained 'then' invocations
+        ///
+        /// The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A
+        /// implies that .then() returns a distinct object.
+        ////
+        /// For compatibility with http://api.jquery.com/category/deferred-object/
+        /// we return this same object. This affects ordering, as
+        /// the jQuery version will fire callbacks in registration
+        /// order regardless of whether they occur on the result
+        /// or the original object.
+        ///
+        /// Compability Note B - Fulfillment value
+        ///
+        /// The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A
+        /// implies that the result of a success callback is the
+        /// fulfillment value of the object and is received by
+        /// other success callbacks that are chained.
+        ///
+        /// For compatibility with http://api.jquery.com/category/deferred-object/
+        /// we disregard this value instead.
+        /// </remarks>
+
+        this._arguments = undefined;
+        this._done = undefined;
+        this._fail = undefined;
+        this._resolved = false;
+        this._rejected = false;
+    };
+
+    DjsDeferred.prototype = {
+        then: function (fulfilledHandler, errorHandler /*, progressHandler */) {
+            /// <summary>Adds success and error callbacks for this deferred object.</summary>
+            /// <param name="fulfilledHandler" type="Function" mayBeNull="true" optional="true">Success callback.</param>
+            /// <param name="errorHandler" type="Function" mayBeNull="true" optional="true">Error callback.</param>
+            /// <remarks>See Compatibility Note A.</remarks>
+
+            if (fulfilledHandler) {
+                if (!this._done) {
+                    this._done = [fulfilledHandler];
+                } else {
+                    this._done.push(fulfilledHandler);
+                }
+            }
+
+            if (errorHandler) {
+                if (!this._fail) {
+                    this._fail = [errorHandler];
+                } else {
+                    this._fail.push(errorHandler);
+                }
+            }
+
+            //// See Compatibility Note A in the DjsDeferred constructor.
+            //// if (!this._next) {
+            ////    this._next = createDeferred();
+            //// }
+            //// return this._next.promise();
+
+            if (this._resolved) {
+                this.resolve.apply(this, this._arguments);
+            } else if (this._rejected) {
+                this.reject.apply(this, this._arguments);
+            }
+
+            return this;
+        },
+
+        resolve: function (/* args */) {
+            /// <summary>Invokes success callbacks for this deferred object.</summary>
+            /// <remarks>All arguments are forwarded to success callbacks.</remarks>
+
+
+            if (this._done) {
+                var i, len;
+                for (i = 0, len = this._done.length; i < len; i++) {
+                    //// See Compability Note B - Fulfillment value.
+                    //// var nextValue =
+                    this._done[i].apply(null, arguments);
+                }
+
+                //// See Compatibility Note A in the DjsDeferred constructor.
+                //// this._next.resolve(nextValue);
+                //// delete this._next;
+
+                this._done = undefined;
+                this._resolved = false;
+                this._arguments = undefined;
+            } else {
+                this._resolved = true;
+                this._arguments = arguments;
+            }
+        },
+
+        reject: function (/* args */) {
+            /// <summary>Invokes error callbacks for this deferred object.</summary>
+            /// <remarks>All arguments are forwarded to error callbacks.</remarks>
+            if (this._fail) {
+                var i, len;
+                for (i = 0, len = this._fail.length; i < len; i++) {
+                    this._fail[i].apply(null, arguments);
+                }
+
+                this._fail = undefined;
+                this._rejected = false;
+                this._arguments = undefined;
+            } else {
+                this._rejected = true;
+                this._arguments = arguments;
+            }
+        },
+
+        promise: function () {
+            /// <summary>Returns a version of this object that has only the read-only methods available.</summary>
+            /// <returns>An object with only the promise object.</returns>
+
+            var result = {};
+            result.then = forwardCall(this, "then", result);
+            return result;
+        }
+    };
+
+    var createDeferred = function () {
+        /// <summary>Creates a deferred object.</summary>
+        /// <returns type="DjsDeferred">
+        /// A new deferred object. If jQuery is installed, then a jQuery
+        /// Deferred object is returned, which provides a superset of features.
+        /// </returns>
+
+        if (window.jQuery && window.jQuery.Deferred) {
+            return new window.jQuery.Deferred();
+        } else {
+            return new DjsDeferred();
+        }
+    };
+
+    // DATAJS INTERNAL START
+    window.datajs.createDeferred = createDeferred;
+    window.datajs.DjsDeferred = DjsDeferred;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/odata-atom.js b/JSLib/src/odata-atom.js
new file mode 100644
index 0000000..d92c2a6
--- /dev/null
+++ b/JSLib/src/odata-atom.js
@@ -0,0 +1,1411 @@
+/// <reference path="odata-utils.js" />
+/// <reference path="odata-handler.js" />
+/// <reference path="odata-xml.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-atom.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // imports
+    var contains = datajs.contains;
+    var djsassert = datajs.djsassert;
+    var isArray = datajs.isArray;
+    var isObject = datajs.isObject;
+    var isXmlNSDeclaration = datajs.isXmlNSDeclaration;
+    var normalizeURI = datajs.normalizeURI;
+    var parseInt10 = datajs.parseInt10;
+    var xmlAppendChild = datajs.xmlAppendChild;
+    var xmlAppendChildren = datajs.xmlAppendChildren
+    var xmlAttributes = datajs.xmlAttributes;
+    var xmlAttributeNode = datajs.xmlAttributeNode;
+    var xmlAttributeValue = datajs.xmlAttributeValue;
+    var xmlBaseURI = datajs.xmlBaseURI;
+    var xmlChildElements = datajs.xmlChildElements;
+    var xmlDom = datajs.xmlDom;
+    var xmlFirstChildElement = datajs.xmlFirstChildElement;
+    var xmlFindElementByPath = datajs.xmlFindElementByPath;
+    var xmlFindNodeByPath = datajs.xmlFindNodeByPath;
+    var xmlInnerText = datajs.xmlInnerText;
+    var xmlLocalName = datajs.xmlLocalName;
+    var xmlNamespaceURI = datajs.xmlNamespaceURI;
+    var xmlNewAttribute = datajs.xmlNewAttribute;
+    var xmlNewElement = datajs.xmlNewElement;
+    var xmlNewFragment = datajs.xmlNewFragment;
+    var xmlNewNodeByPath = datajs.xmlNewNodeByPath;
+    var xmlNewNSDeclaration = datajs.xmlNewNSDeclaration;
+    var xmlNewText = datajs.xmlNewText;
+    var xmlNodeValue = datajs.xmlNodeValue;
+    var xmlNS = datajs.xmlNS;
+    var xmlnsNS = datajs.xmlnsNS;
+    var xmlQualifiedName = datajs.xmlQualifiedName;
+    var xmlParse = datajs.xmlParse;
+    var xmlSerialize = datajs.xmlSerialize;
+    var xmlSerializeDescendants = datajs.xmlSerializeDescendants;
+    var xmlSibling = datajs.xmlSibling;
+    var w3org = datajs.w3org;
+
+    var adoDs = odata.adoDs;
+    var contentType = odata.contentType;
+    var createAttributeExtension = odata.createAttributeExtension;
+    var createElementExtension = odata.createElementExtension;
+    var handler = odata.handler;
+    var isPrimitiveEdmType = odata.isPrimitiveEdmType;
+    var isFeed = odata.isFeed;
+    var isNamedStream = odata.isNamedStream;
+    var lookupEntityType = odata.lookupEntityType;
+    var lookupComplexType = odata.lookupComplexType;
+    var lookupProperty = odata.lookupProperty;
+    var navigationPropertyKind = odata.navigationPropertyKind;
+    var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
+    var maxVersion = odata.maxVersion;
+    var odataXmlNs = odata.odataXmlNs;
+    var odataMetaXmlNs = odata.odataMetaXmlNs;
+    var odataMetaPrefix = odata.odataMetaPrefix;
+    var odataPrefix = odata.odataPrefix;
+    var odataRelatedPrefix = odata.odataRelatedPrefix;
+    var odataScheme = odata.odataScheme;
+    var parseBool = odata.parseBool;
+    var parseDateTime = odata.parseDateTime;
+    var parseDateTimeOffset = odata.parseDateTimeOffset;
+    var parseDuration = odata.parseDuration;
+    var parseTimezone = odata.parseTimezone;
+    var xmlNewODataElement = odata.xmlNewODataElement;
+    var xmlNewODataElementInfo = odata.xmlNewODataElementInfo;
+    var xmlNewODataMetaAttribute = odata.xmlNewODataMetaAttribute;
+    var xmlNewODataMetaElement = odata.xmlNewODataMetaElement;
+    var xmlNewODataDataElement = odata.xmlNewODataDataElement;
+    var xmlReadODataEdmPropertyValue = odata.xmlReadODataEdmPropertyValue;
+    var xmlReadODataProperty = odata.xmlReadODataProperty;
+
+    // CONTENT START
+
+    var atomPrefix = "a";
+
+    var atomXmlNs = w3org + "2005/Atom";                    // http://www.w3.org/2005/Atom
+    var appXmlNs = w3org + "2007/app";                      // http://www.w3.org/2007/app
+
+    var odataEditMediaPrefix = adoDs + "/edit-media/";        // http://schemas.microsoft.com/ado/2007/08/dataservices/edit-media
+    var odataMediaResourcePrefix = adoDs + "/mediaresource/"; // http://schemas.microsoft.com/ado/2007/08/dataservices/mediaresource
+    var odataRelatedLinksPrefix = adoDs + "/relatedlinks/";   // http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks
+
+    var atomAcceptTypes = ["application/atom+xml", "application/atomsvc+xml", "application/xml"];
+    var atomMediaType = atomAcceptTypes[0];
+
+    // These are the namespaces that are not considered ATOM extension namespaces.
+    var nonExtensionNamepaces = [atomXmlNs, appXmlNs, xmlNS, xmlnsNS];
+
+    // These are entity property mapping paths that have well-known paths.
+    var knownCustomizationPaths = {
+        SyndicationAuthorEmail: "author/email",
+        SyndicationAuthorName: "author/name",
+        SyndicationAuthorUri: "author/uri",
+        SyndicationContributorEmail: "contributor/email",
+        SyndicationContributorName: "contributor/name",
+        SyndicationContributorUri: "contributor/uri",
+        SyndicationPublished: "published",
+        SyndicationRights: "rights",
+        SyndicationSummary: "summary",
+        SyndicationTitle: "title",
+        SyndicationUpdated: "updated"
+    };
+
+    var expandedFeedCustomizationPath = function (path) {
+        /// <summary>Returns an expanded customization path if it's well-known.</summary>
+        /// <param name="path" type="String">Path to expand.</param>
+        /// <returns type="String">Expanded path or just 'path' otherwise.</returns>
+
+        return knownCustomizationPaths[path] || path;
+    };
+
+    var isExtensionNs = function (nsURI) {
+        /// <summary>Checks whether the specified namespace is an extension namespace to ATOM.</summary>
+        /// <param type="String" name="nsURI">Namespace to check.</param>
+        /// <returns type="Boolean">true if nsURI is an extension namespace to ATOM; false otherwise.</returns>
+
+        return !(contains(nonExtensionNamepaces, nsURI));
+    };
+
+    var atomFeedCustomization = function (customizationModel, entityType, model, propertyName, suffix) {
+        /// <summary>Creates an object describing a feed customization that was delcared in an OData conceptual schema.</summary>
+        /// <param name="customizationModel" type="Object">Object describing the customization delcared in the conceptual schema.</param>
+        /// <param name="entityType" type="Object">Object describing the entity type that owns the customization in an OData conceputal schema.</param>
+        /// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
+        /// <param name="propertyName" type="String" optional="true">Name of the property to which this customization applies.</param>
+        /// <param name="suffix" type="String" optional="true">Suffix to feed customization properties in the conceptual schema.</param>
+        /// <returns type="Object">Object that describes an applicable feed customization.</returns>
+
+        suffix = suffix || "";
+        var targetPath = customizationModel["FC_TargetPath" + suffix];
+        if (!targetPath) {
+            return null;
+        }
+
+        var sourcePath = customizationModel["FC_SourcePath" + suffix];
+        var targetXmlPath = expandedFeedCustomizationPath(targetPath);
+
+        var propertyPath = propertyName ? propertyName + (sourcePath ? "/" + sourcePath : "") : sourcePath;
+        var propertyType = propertyPath && lookupPropertyType(model, entityType, propertyPath);
+        var nsURI = customizationModel["FC_NsUri" + suffix] || null;
+        var nsPrefix = customizationModel["FC_NsPrefix" + suffix] || null;
+        var keepinContent = customizationModel["FC_KeepInContent" + suffix] || "";
+
+        if (targetPath !== targetXmlPath) {
+            nsURI = atomXmlNs;
+            nsPrefix = atomPrefix;
+        }
+
+        return {
+            contentKind: customizationModel["FC_ContentKind" + suffix],
+            keepInContent: keepinContent.toLowerCase() === "true",
+            nsPrefix: nsPrefix,
+            nsURI: nsURI,
+            propertyPath: propertyPath,
+            propertyType: propertyType,
+            entryPath: targetXmlPath
+        };
+    };
+
+    var atomApplyAllFeedCustomizations = function (entityType, model, callback) {
+        /// <summary>Gets all the feed customizations that have to be applied to an entry as per the enity type declared in an OData conceptual schema.</summary>
+        /// <param name="entityType" type="Object">Object describing an entity type in a conceptual schema.</param>
+        /// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
+        /// <param name="callback" type="Function">Callback function to be invoked for each feed customization that needs to be applied.</param>
+
+        var customizations = [];
+        while (entityType) {
+            var sourcePath = entityType.FC_SourcePath;
+            var customization = atomFeedCustomization(entityType, entityType, model);
+            if (customization) {
+                callback(customization);
+            }
+
+            var properties = entityType.property || [];
+            var i, len;
+            for (i = 0, len = properties.length; i < len; i++) {
+                var property = properties[i];
+                var suffixCounter = 0;
+                var suffix = "";
+
+                while (customization = atomFeedCustomization(property, entityType, model, property.name, suffix)) {
+                    callback(customization);
+                    suffixCounter++;
+                    suffix = "_" + suffixCounter;
+                }
+            }
+            entityType = lookupEntityType(entityType.baseType, model);
+        }
+        return customizations;
+    };
+
+    var atomReadExtensionAttributes = function (domElement) {
+        /// <summary>Reads ATOM extension attributes (any attribute not in the Atom namespace) from a DOM element.</summary>
+        /// <param name="domElement">DOM element with zero or more extension attributes.</param>
+        /// <returns type="Array">An array of extension attribute representations.</returns>
+
+        var extensions = [];
+        xmlAttributes(domElement, function (attribute) {
+            var nsURI = xmlNamespaceURI(attribute);
+            if (isExtensionNs(nsURI)) {
+                extensions.push(createAttributeExtension(attribute, true));
+            }
+        });
+        return extensions;
+    };
+
+    var atomReadExtensionElement = function (domElement) {
+        /// <summary>Reads an ATOM extension element (an element not in the ATOM namespaces).</summary>
+        /// <param name="domElement">DOM element not part of the atom namespace.</param>
+        /// <returns type="Object">Object representing the extension element.</returns>
+
+        return createElementExtension(domElement, /*addNamespaceURI*/true);
+    };
+
+    var atomReadDocument = function (domElement, baseURI, model) {
+        /// <summary>Reads an ATOM entry, feed or service document, producing an object model in return.</summary>
+        /// <param name="domElement">Top-level ATOM DOM element to read.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the ATOM document.</param>
+        /// <param name="model" type="Object">Object that describes the conceptual schema.</param>
+        /// <returns type="Object">The object model representing the specified element, undefined if the top-level element is not part of the ATOM specification.</returns>
+
+        var nsURI = xmlNamespaceURI(domElement);
+        var localName = xmlLocalName(domElement);
+
+        // Handle service documents.
+        if (nsURI === appXmlNs && localName === "service") {
+            return atomReadServiceDocument(domElement, baseURI);
+        }
+
+        // Handle feed and entry elements.
+        if (nsURI === atomXmlNs) {
+            if (localName === "feed") {
+                return atomReadFeed(domElement, baseURI, model);
+            }
+            if (localName === "entry") {
+                return atomReadEntry(domElement, baseURI, model);
+            }
+        }
+
+        // Allow undefined to be returned.
+    };
+
+    var atomReadAdvertisedActionOrFunction = function (domElement, baseURI) {
+        /// <summary>Reads the DOM element for an action or a function in an OData Atom document.</summary>
+        /// <param name="domElement">DOM element to read.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing the action or function target url.</param>
+        /// <returns type="Object">Object with title, target, and metadata fields.</returns>
+
+        var extensions = [];
+        var result = { extensions: extensions };
+        xmlAttributes(domElement, function (attribute) {
+            var localName = xmlLocalName(attribute);
+            var nsURI = xmlNamespaceURI(attribute);
+            var value = xmlNodeValue(attribute);
+
+            if (nsURI === null) {
+                if (localName === "title" || localName === "metadata") {
+                    result[localName] = value;
+                    return;
+                }
+                if (localName === "target") {
+                    result.target = normalizeURI(value, xmlBaseURI(domElement, baseURI));
+                    return;
+                }
+            }
+
+            if (isExtensionNs(nsURI)) {
+                extensions.push(createAttributeExtension(attribute, true));
+            }
+        });
+        return result;
+    };
+
+    var atomReadAdvertisedAction = function (domElement, baseURI, parentMetadata) {
+        /// <summary>Reads the DOM element for an action in an OData Atom document.</summary>
+        /// <param name="domElement">DOM element to read.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing the action or target url.</param>
+        /// <param name="parentMetadata" type="Object">Object to update with the action metadata.</param>
+
+        var actions = parentMetadata.actions = parentMetadata.actions || [];
+        actions.push(atomReadAdvertisedActionOrFunction(domElement, baseURI));
+    };
+
+    var atomReadAdvertisedFunction = function (domElement, baseURI, parentMetadata) {
+        /// <summary>Reads the DOM element for an action in an OData Atom document.</summary>
+        /// <param name="domElement">DOM element to read.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing the action or target url.</param>
+        /// <param name="parentMetadata" type="Object">Object to update with the action metadata.</param>
+
+        var functions = parentMetadata.functions = parentMetadata.functions || [];
+        functions.push(atomReadAdvertisedActionOrFunction(domElement, baseURI));
+    };
+
+    var atomReadFeed = function (domElement, baseURI, model) {
+        /// <summary>Reads a DOM element for an ATOM feed, producing an object model in return.</summary>
+        /// <param name="domElement">ATOM feed DOM element.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the ATOM feed.</param>
+        /// <param name="model">Metadata that describes the conceptual schema.</param>
+        /// <returns type="Object">A new object representing the feed.</returns>
+
+        var extensions = atomReadExtensionAttributes(domElement);
+        var feedMetadata = { feed_extensions: extensions };
+        var results = [];
+
+        var feed = { __metadata: feedMetadata, results: results };
+
+        baseURI = xmlBaseURI(domElement, baseURI);
+
+        xmlChildElements(domElement, function (child) {
+            var nsURI = xmlNamespaceURI(child);
+            var localName = xmlLocalName(child);
+
+            if (nsURI === odataMetaXmlNs) {
+                if (localName === "count") {
+                    feed.__count = parseInt(xmlInnerText(child), 10);
+                    return;
+                }
+                if (localName === "action") {
+                    atomReadAdvertisedAction(child, baseURI, feedMetadata);
+                    return;
+                }
+                if (localName === "function") {
+                    atomReadAdvertisedFunction(child, baseURI, feedMetadata);
+                    return;
+                }
+            }
+
+            if (isExtensionNs(nsURI)) {
+                extensions.push(createElementExtension(child));
+                return;
+            }
+
+            // The element should belong to the ATOM namespace.
+            djsassert(nsURI === atomXmlNs, "atomReadFeed - child feed element is not in the atom namespace!!");
+
+            if (localName === "entry") {
+                results.push(atomReadEntry(child, baseURI, model));
+                return;
+            }
+            if (localName === "link") {
+                atomReadFeedLink(child, feed, baseURI);
+                return;
+            }
+            if (localName === "id") {
+                feedMetadata.uri = normalizeURI(xmlInnerText(child), baseURI);
+                feedMetadata.uri_extensions = atomReadExtensionAttributes(child);
+                return;
+            }
+            if (localName === "title") {
+                feedMetadata.title = xmlInnerText(child) || "";
+                feedMetadata.title_extensions = atomReadExtensionAttributes(child);
+                return;
+            }
+        });
+
+        return feed;
+    };
+
+    var atomReadFeedLink = function (domElement, feed, baseURI) {
+        /// <summary>Reads an ATOM link DOM element for a feed.</summary>
+        /// <param name="domElement">ATOM link DOM element.</param>
+        /// <param name="feed">Feed object to be annotated with the link data.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+
+        var link = atomReadLink(domElement, baseURI);
+        var href = link.href;
+        var rel = link.rel;
+        var extensions = link.extensions;
+        var metadata = feed.__metadata;
+
+        if (rel === "next") {
+            feed.__next = href;
+            metadata.next_extensions = extensions;
+            return;
+        }
+        if (rel === "self") {
+            metadata.self = href;
+            metadata.self_extensions = extensions;
+            return;
+        }
+    };
+
+    var atomReadLink = function (domElement, baseURI) {
+        /// <summary>Reads an ATOM link DOM element.</summary>
+        /// <param name="linkElement">DOM element to read.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing the link href.</param>
+        /// <returns type="Object">A link element representation.</returns>
+
+        baseURI = xmlBaseURI(domElement, baseURI);
+
+        var extensions = [];
+        var link = { extensions: extensions, baseURI: baseURI };
+
+        xmlAttributes(domElement, function (attribute) {
+            var nsURI = xmlNamespaceURI(attribute);
+            var localName = xmlLocalName(attribute);
+            var value = attribute.value;
+
+            if (localName === "href") {
+                link.href = normalizeURI(value, baseURI);
+                return;
+            }
+            if (localName === "type" || localName === "rel") {
+                link[localName] = value;
+                return;
+            }
+
+            if (isExtensionNs(nsURI)) {
+                extensions.push(createAttributeExtension(attribute, true));
+            }
+        });
+
+        if (!link.href) {
+            throw { error: "href attribute missing on link element", element: domElement };
+        }
+
+        return link;
+    };
+
+    var atomGetObjectValueByPath = function (path, item) {
+        /// <summary>Gets a slashed path value from the specified item.</summary>
+        /// <param name="path" type="String">Property path to read ('/'-separated).</param>
+        /// <param name="item" type="Object">Object to get value from.</param>
+        /// <returns>The property value, possibly undefined if any path segment is missing.</returns>
+
+        // Fast path.
+        if (path.indexOf('/') === -1) {
+            return item[path];
+        } else {
+            var parts = path.split('/');
+            var i, len;
+            for (i = 0, len = parts.length; i < len; i++) {
+                // Avoid traversing a null object.
+                if (item === null) {
+                    return undefined;
+                }
+
+                item = item[parts[i]];
+                if (item === undefined) {
+                    return item;
+                }
+            }
+
+            return item;
+        }
+    };
+
+    var atomSetEntryValueByPath = function (path, target, value, propertyType) {
+        /// <summary>Sets a slashed path value on the specified target.</summary>
+        /// <param name="path" type="String">Property path to set ('/'-separated).</param>
+        /// <param name="target" type="Object">Object to set value on.</param>
+        /// <param name="value">Value to set.</param>
+        /// <param name="propertyType" type="String" optional="true">Property type to set in metadata.</param>
+
+        var propertyName;
+        if (path.indexOf('/') === -1) {
+            target[path] = value;
+            propertyName = path;
+        } else {
+            var parts = path.split('/');
+            var i, len;
+            for (i = 0, len = (parts.length - 1); i < len; i++) {
+                // We construct each step of the way if the property is missing;
+                // if it's already initialized to null, we stop further processing.
+                var next = target[parts[i]];
+                if (next === undefined) {
+                    next = {};
+                    target[parts[i]] = next;
+                } else if (next === null) {
+                    return;
+                }
+                target = next;
+            }
+            propertyName = parts[i];
+            target[propertyName] = value;
+        }
+
+        if (propertyType) {
+            var metadata = target.__metadata = target.__metadata || {};
+            var properties = metadata.properties = metadata.properties || {};
+            var property = properties[propertyName] = properties[propertyName] || {};
+            property.type = propertyType;
+        }
+    };
+
+    var atomApplyCustomizationToEntryObject = function (customization, domElement, entry) {
+        /// <summary>Applies a specific feed customization item to an object.</summary>
+        /// <param name="customization">Object with customization description.</param>
+        /// <param name="sourcePath">Property path to set ('source' in the description).</param>
+        /// <param name="entryElement">XML element for the entry that corresponds to the object being read.</param>
+        /// <param name="entryObject">Object being read.</param>
+        /// <param name="propertyType" type="String">Name of property type to set.</param>
+        /// <param name="suffix" type="String">Suffix to feed customization properties.</param>
+
+        var propertyPath = customization.propertyPath;
+        // If keepInConent equals true or the property value is null we do nothing as this overrides any other customization.
+        if (customization.keepInContent || atomGetObjectValueByPath(propertyPath, entry) === null) {
+            return;
+        }
+
+        var xmlNode = xmlFindNodeByPath(domElement, customization.nsURI, customization.entryPath);
+
+        // If the XML tree does not contain the necessary elements to read the value,
+        // then it shouldn't be considered null, but rather ignored at all. This prevents
+        // the customization from generating the object path down to the property.
+        if (!xmlNode) {
+            return;
+        }
+
+        var propertyType = customization.propertyType;
+        var propertyValue;
+
+        if (customization.contentKind === "xhtml") {
+            // Treat per XHTML in http://tools.ietf.org/html/rfc4287#section-3.1.1, including the DIV
+            // in the content.
+            propertyValue = xmlSerializeDescendants(xmlNode);
+        } else {
+            propertyValue = xmlReadODataEdmPropertyValue(xmlNode, propertyType || "Edm.String");
+        }
+        // Set the value on the entry.
+        atomSetEntryValueByPath(propertyPath, entry, propertyValue, propertyType);
+    };
+
+    var lookupPropertyType = function (metadata, owningType, path) {
+        /// <summary>Looks up the type of a property given its path in an entity type.</summary>
+        /// <param name="metadata">Metadata in which to search for base and complex types.</param>
+        /// <param name="owningType">Type to which property belongs.</param>
+        /// <param name="path" type="String" mayBeNull="false">Property path to look at.</param>
+        /// <returns type="String">The name of the property type; possibly null.</returns>
+
+        var parts = path.split("/");
+        var i, len;
+        while (owningType) {
+            // Keep track of the type being traversed, necessary for complex types.
+            var traversedType = owningType;
+
+            for (i = 0, len = parts.length; i < len; i++) {
+                // Traverse down the structure as necessary.
+                var properties = traversedType.property;
+                if (!properties) {
+                    break;
+                }
+
+                // Find the property by scanning the property list (might be worth pre-processing).
+                var propertyFound = lookupProperty(properties, parts[i]);
+                if (!propertyFound) {
+                    break;
+                }
+
+                var propertyType = propertyFound.type;
+
+                // We could in theory still be missing types, but that would
+                // be caused by a malformed path.
+                if (!propertyType || isPrimitiveEdmType(propertyType)) {
+                    return propertyType || null;
+                }
+
+                traversedType = lookupComplexType(propertyType, metadata);
+                if (!traversedType) {
+                    return null;
+                }
+            }
+
+            // Traverse up the inheritance chain.
+            owningType = lookupEntityType(owningType.baseType, metadata);
+        }
+
+        return null;
+    };
+
+    var atomReadEntry = function (domElement, baseURI, model) {
+        /// <summary>Reads a DOM element for an ATOM entry, producing an object model in return.</summary>
+        /// <param name="domElement">ATOM entry DOM element.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the ATOM entry.</param>
+        /// <param name="model">Metadata that describes the conceptual schema.</param>
+        /// <returns type="Object">A new object representing the entry.</returns>
+
+        var entryMetadata = {};
+        var entry = { __metadata: entryMetadata };
+
+        var etag = xmlAttributeValue(domElement, "etag", odataMetaXmlNs);
+        if (etag) {
+            entryMetadata.etag = etag;
+        }
+
+        baseURI = xmlBaseURI(domElement, baseURI);
+
+        xmlChildElements(domElement, function (child) {
+            var nsURI = xmlNamespaceURI(child);
+            var localName = xmlLocalName(child);
+
+            if (nsURI === atomXmlNs) {
+                if (localName === "id") {
+                    atomReadEntryId(child, entryMetadata, baseURI);
+                    return;
+                }
+                if (localName === "category") {
+                    atomReadEntryType(child, entryMetadata);
+                    return;
+                }
+                if (localName === "content") {
+                    atomReadEntryContent(child, entry, entryMetadata, baseURI);
+                    return;
+                }
+                if (localName === "link") {
+                    atomReadEntryLink(child, entry, entryMetadata, baseURI, model);
+                    return;
+                }
+                return;
+            }
+
+            if (nsURI === odataMetaXmlNs) {
+                if (localName === "properties") {
+                    atomReadEntryStructuralObject(child, entry, entryMetadata);
+                    return;
+                }
+                if (localName === "action") {
+                    atomReadAdvertisedAction(child, baseURI, entryMetadata);
+                    return;
+                }
+                if (localName === "function") {
+                    atomReadAdvertisedFunction(child, baseURI, entryMetadata);
+                    return;
+                }
+            }
+        });
+
+        // Apply feed customizations if applicable
+        var entityType = lookupEntityType(entryMetadata.type, model);
+        atomApplyAllFeedCustomizations(entityType, model, function (customization) {
+            atomApplyCustomizationToEntryObject(customization, domElement, entry);
+        });
+
+        return entry;
+    };
+
+    var atomReadEntryId = function (domElement, entryMetadata, baseURI) {
+        /// <summary>Reads an ATOM entry id DOM element.</summary>
+        /// <param name="domElement">ATOM id DOM element.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the id information.</param>
+
+        entryMetadata.uri = normalizeURI(xmlInnerText(domElement), xmlBaseURI(domElement, baseURI));
+        entryMetadata.uri_extensions = atomReadExtensionAttributes(domElement);
+    };
+
+    var atomReadEntryType = function (domElement, entryMetadata) {
+        /// <summary>Reads type information from an ATOM category DOM element.</summary>
+        /// <param name="domElement">ATOM category DOM element.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the type information.</param>
+
+        if (xmlAttributeValue(domElement, "scheme") === odataScheme) {
+            if (entryMetadata.type) {
+                throw { message: "Invalid AtomPub document: multiple category elements defining the entry type were encounterd withing an entry", element: domElement };
+            }
+
+            var typeExtensions = [];
+            xmlAttributes(domElement, function (attribute) {
+                var nsURI = xmlNamespaceURI(attribute);
+                var localName = xmlLocalName(attribute);
+
+                if (!nsURI) {
+                    if (localName !== "scheme" && localName !== "term") {
+                        typeExtensions.push(createAttributeExtension(attribute, true));
+                    }
+                    return;
+                }
+
+                if (isExtensionNs(nsURI)) {
+                    typeExtensions.push(createAttributeExtension(attribute, true));
+                }
+            });
+
+            entryMetadata.type = xmlAttributeValue(domElement, "term");
+            entryMetadata.type_extensions = typeExtensions;
+        }
+    };
+
+    var atomReadEntryContent = function (domElement, entry, entryMetadata, baseURI) {
+        /// <summary>Reads an ATOM content DOM element.</summary>
+        /// <param name="domElement">ATOM content DOM element.</param>
+        /// <param name="entry">Entry object to update with information.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the content information.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the Atom entry content.</param>
+
+        var src = xmlAttributeValue(domElement, "src");
+        var type = xmlAttributeValue(domElement, "type");
+
+        if (src) {
+            if (!type) {
+                throw {
+                    message: "Invalid AtomPub document: content element must specify the type attribute if the src attribute is also specified",
+                    element: domElement
+                };
+            }
+
+            entryMetadata.media_src = normalizeURI(src, xmlBaseURI(domElement, baseURI));
+            entryMetadata.content_type = type;
+        }
+
+        xmlChildElements(domElement, function (child) {
+            if (src) {
+                throw { message: "Invalid AtomPub document: content element must not have child elements if the src attribute is specified", element: domElement };
+            }
+
+            if (xmlNamespaceURI(child) === odataMetaXmlNs && xmlLocalName(child) === "properties") {
+                atomReadEntryStructuralObject(child, entry, entryMetadata);
+            }
+        });
+    };
+
+    var atomReadEntryLink = function (domElement, entry, entryMetadata, baseURI, model) {
+        /// <summary>Reads a link element on an entry.</summary>
+        /// <param name="atomEntryLink">'link' element on the entry.</param>
+        /// <param name="entry" type="Object">Entry object to update with the link data.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the link metadata.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing the link href.</param>
+        /// <param name="model" type="Object">Metadata that describes the conceptual schema.</param>
+
+        var link = atomReadLink(domElement, baseURI);
+
+        var rel = link.rel;
+        var href = link.href;
+        var extensions = link.extensions;
+
+        if (rel === "self") {
+            entryMetadata.self = href;
+            entryMetadata.self_link_extensions = extensions;
+            return;
+        }
+
+        if (rel === "edit") {
+            entryMetadata.edit = href;
+            entryMetadata.edit_link_extensions = extensions;
+            return;
+        }
+
+        if (rel === "edit-media") {
+            entryMetadata.edit_media = link.href;
+            entryMetadata.edit_media_extensions = extensions;
+            atomReadLinkMediaEtag(link, entryMetadata);
+            return;
+        }
+
+        // This might be a named stream edit link
+        if (rel.indexOf(odataEditMediaPrefix) === 0) {
+            atomReadNamedStreamEditLink(link, entry, entryMetadata);
+            return;
+        }
+
+        // This might be a named stram media resource (read) link
+        if (rel.indexOf(odataMediaResourcePrefix) === 0) {
+            atomReadNamedStreamSelfLink(link, entry, entryMetadata);
+            return;
+        }
+
+        // This might be a navigation property
+        if (rel.indexOf(odataRelatedPrefix) === 0) {
+            atomReadNavPropLink(domElement, link, entry, entryMetadata, model);
+            return;
+        }
+
+        if (rel.indexOf(odataRelatedLinksPrefix) === 0) {
+            atomReadNavPropRelatedLink(link, entryMetadata);
+            return;
+        }
+    };
+
+    var atomReadNavPropRelatedLink = function (link, entryMetadata) {
+        /// <summary>Reads a link represnting the links related to a navigation property in an OData Atom document.</summary>
+        /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
+        /// <param name="entryMetadata" type="Object">Entry metadata object to update with the related links information.</param>
+
+        var propertyName = link.rel.substring(odataRelatedLinksPrefix.length);
+        djsassert(propertyName, "atomReadNavPropRelatedLink - property name is null, empty or undefined!");
+
+        // Set the extra property information on the entry object metadata.
+        entryMetadata.properties = entryMetadata.properties || {};
+        var propertyMetadata = entryMetadata.properties[propertyName] = entryMetadata.properties[propertyName] || {};
+
+        propertyMetadata.associationuri = link.href;
+        propertyMetadata.associationuri_extensions = link.extensions;
+    };
+
+    var atomReadNavPropLink = function (domElement, link, entry, entryMetadata, model) {
+        /// <summary>Reads a link representing a navigation property in an OData Atom document.</summary>
+        /// <param name="domElement">DOM element for a navigation property in an OData Atom document.</summary>
+        /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
+        /// <param name="entry" type="Object">Entry object to update with the navigation property.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the navigation property metadata.</param>
+        /// <param name="model" type="Object">Metadata that describes the conceptual schema.</param>
+
+        // Get any inline data.
+        var inlineData;
+        var inlineElement = xmlFirstChildElement(domElement, odataMetaXmlNs, "inline");
+        if (inlineElement) {
+            var inlineDocRoot = xmlFirstChildElement(inlineElement);
+            var inlineBaseURI = xmlBaseURI(inlineElement, link.baseURI);
+            inlineData = inlineDocRoot ? atomReadDocument(inlineDocRoot, inlineBaseURI, model) : null;
+        } else {
+            // If the link has no inline content, we consider it deferred.
+            inlineData = { __deferred: { uri: link.href} };
+        }
+
+        var propertyName = link.rel.substring(odataRelatedPrefix.length);
+
+        // Set the property value on the entry object.
+        entry[propertyName] = inlineData;
+
+        // Set the extra property information on the entry object metadata.
+        entryMetadata.properties = entryMetadata.properties || {};
+        var propertyMetadata = entryMetadata.properties[propertyName] = entryMetadata.properties[propertyName] || {};
+
+        propertyMetadata.extensions = link.extensions;
+    };
+
+    var atomReadNamedStreamEditLink = function (link, entry, entryMetadata) {
+        /// <summary>Reads a link representing the edit-media url of a named stream in an OData Atom document.</summary>
+        /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
+        /// <param name="entry" type="Object">Entry object to update with the named stream data.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the named stream metadata.</param>
+
+        var propertyName = link.rel.substring(odataEditMediaPrefix.length);
+        djsassert(propertyName, "atomReadNamedStreamEditLink - property name is null, empty or undefined!");
+
+        var namedStreamMediaResource = atomGetEntryNamedStreamMediaResource(propertyName, entry, entryMetadata);
+        var mediaResource = namedStreamMediaResource.value;
+        var mediaResourceMetadata = namedStreamMediaResource.metadata;
+
+        var editMedia = link.href;
+
+        mediaResource.edit_media = editMedia;
+        mediaResource.content_type = link.type;
+        mediaResourceMetadata.edit_media_extensions = link.extensions;
+
+        // If there is only the edit link, make it the media self link as well.
+        mediaResource.media_src = mediaResource.media_src || editMedia;
+        mediaResourceMetadata.media_src_extensions = mediaResourceMetadata.media_src_extensions || [];
+
+        atomReadLinkMediaEtag(link, mediaResource);
+    };
+
+    var atomReadNamedStreamSelfLink = function (link, entry, entryMetadata) {
+        /// <summary>Reads a link representing the self url of a named stream in an OData Atom document.</summary>
+        /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
+        /// <param name="entry" type="Object">Entry object to update with the named stream data.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the named stream metadata.</param>
+
+        var propertyName = link.rel.substring(odataMediaResourcePrefix.length);
+        djsassert(propertyName, "atomReadNamedStreamEditLink - property name is null, empty or undefined!");
+
+        var namedStreamMediaResource = atomGetEntryNamedStreamMediaResource(propertyName, entry, entryMetadata);
+        var mediaResource = namedStreamMediaResource.value;
+        var mediaResourceMetadata = namedStreamMediaResource.metadata;
+
+        mediaResource.media_src = link.href;
+        mediaResourceMetadata.media_src_extensions = link.extensions;
+        mediaResource.content_type = link.type;
+    };
+
+    var atomGetEntryNamedStreamMediaResource = function (name, entry, entryMetadata) {
+        /// <summary>Gets the media resource object and metadata object for a named stream in an entry object.</summary>
+        /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
+        /// <param name="entry" type="Object">Entry object from which the media resource object will be obtained.</param>
+        /// <param name="entryMetadata" type="Object">Entry metadata object from which the media resource metadata object will be obtained.</param>
+        /// <remarks>
+        ///    If the entry doest' have a media resource for the named stream indicated by the name argument, then this function will create a new
+        ///    one along with its metadata object.
+        /// <remarks>
+        /// <returns type="Object"> Object containing the value and metadata of the named stream's media resource. <returns>
+
+        entryMetadata.properties = entryMetadata.properties || {};
+
+        var mediaResourceMetadata = entryMetadata.properties[name];
+        var mediaResource = entry[name] && entry[name].__mediaresource;
+
+        if (!mediaResource) {
+            mediaResource = {};
+            entry[name] = { __mediaresource: mediaResource };
+            entryMetadata.properties[name] = mediaResourceMetadata = {};
+        }
+        return { value: mediaResource, metadata: mediaResourceMetadata };
+    };
+
+    var atomReadLinkMediaEtag = function (link, mediaResource) {
+        /// <summary>Gets the media etag from the link extensions and updates the media resource object with it.</summary>
+        /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
+        /// <param name="mediaResource" type="Object">Object containing media information for an OData Atom entry.</param>
+        /// <remarks>
+        ///    The function will remove the extension object for the etag if it finds it in the link extensions and will set
+        ///    its value under the media_etag property of the mediaResource object.
+        /// <remarks>
+        /// <returns type="Object"> Object containing the value and metadata of the named stream's media resource. <returns>
+
+        var extensions = link.extensions;
+        var i, len;
+        for (i = 0, len = extensions.length; i < len; i++) {
+            if (extensions[i].namespaceURI === odataMetaXmlNs && extensions[i].name === "etag") {
+                mediaResource.media_etag = extensions[i].value;
+                extensions.splice(i, 1);
+                return;
+            }
+        }
+    };
+
+    var atomReadEntryStructuralObject = function (domElement, parent, parentMetadata) {
+        /// <summary>Reads an atom entry's property as a structural object and sets its value in the parent and the metadata in the parentMetadata objects.</summary>
+        /// <param name="propertiesElement">XML element for the 'properties' node.</param>
+        /// <param name="parent">
+        ///     Object that will contain the property value. It can be either an antom entry or
+        ///     an atom complex property object.
+        /// </param>
+        /// <param name="parentMetadata">Object that will contain the property metadata. It can be either an atom entry metadata or a complex property metadata object</param>
+
+        xmlChildElements(domElement, function (child) {
+            var property = xmlReadODataProperty(child);
+            if (property) {
+                var propertyName = property.name;
+                var propertiesMetadata = parentMetadata.properties = parentMetadata.properties || {};
+                propertiesMetadata[propertyName] = property.metadata;
+                parent[propertyName] = property.value;
+            }
+        });
+    };
+
+    var atomReadServiceDocument = function (domElement, baseURI) {
+        /// <summary>Reads an AtomPub service document</summary>
+        /// <param name="atomServiceDoc">DOM element for the root of an AtomPub service document</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the AtomPub service document.</param>
+        /// <returns type="Object">An object that contains the properties of the service document</returns>
+
+        var workspaces = [];
+        var extensions = [];
+
+        baseURI = xmlBaseURI(domElement, baseURI);
+        // Find all the workspace elements.
+        xmlChildElements(domElement, function (child) {
+            if (xmlNamespaceURI(child) === appXmlNs && xmlLocalName(child) === "workspace") {
+                workspaces.push(atomReadServiceDocumentWorkspace(child, baseURI));
+                return;
+            }
+            extensions.push(createElementExtension(child));
+        });
+
+        // AtomPub (RFC 5023 Section 8.3.1) says a service document MUST contain one or
+        // more workspaces. Throw if we don't find any.
+        if (workspaces.length === 0) {
+            throw { message: "Invalid AtomPub service document: No workspace element found.", element: domElement };
+        }
+
+        return { workspaces: workspaces, extensions: extensions };
+    };
+
+    var atomReadServiceDocumentWorkspace = function (domElement, baseURI) {
+        /// <summary>Reads a single workspace element from an AtomPub service document</summary>
+        /// <param name="domElement">DOM element that represents a workspace of an AtomPub service document</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the AtomPub service document workspace.</param>
+        /// <returns type="Object">An object that contains the properties of the workspace</returns>
+
+        var collections = [];
+        var extensions = [];
+        var title; // = undefined;
+
+        baseURI = xmlBaseURI(domElement, baseURI);
+
+        xmlChildElements(domElement, function (child) {
+            var nsURI = xmlNamespaceURI(child);
+            var localName = xmlLocalName(child);
+
+            if (nsURI === atomXmlNs) {
+                if (localName === "title") {
+                    if (title !== undefined) {
+                        throw { message: "Invalid AtomPub service document: workspace has more than one child title element", element: child };
+                    }
+
+                    title = xmlInnerText(child);
+                    return;
+                }
+            }
+
+            if (nsURI === appXmlNs) {
+                if (localName === "collection") {
+                    collections.push(atomReadServiceDocumentCollection(child, baseURI));
+                }
+                return;
+            }
+            extensions.push(atomReadExtensionElement(child));
+        });
+
+        return { title: title || "", collections: collections, extensions: extensions };
+    };
+
+    var atomReadServiceDocumentCollection = function (domElement, baseURI) {
+        /// <summary>Reads a service document collection element into an object.</summary>
+        /// <param name="domElement">DOM element that represents a collection of an AtomPub service document.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the AtomPub service document collection.</param>
+        /// <returns type="Object">An object that contains the properties of the collection.</returns>
+
+
+        var href = xmlAttributeValue(domElement, "href");
+
+        if (!href) {
+            throw { message: "Invalid AtomPub service document: collection has no href attribute", element: domElement };
+        }
+
+        baseURI = xmlBaseURI(domElement, baseURI);
+        href = normalizeURI(href, xmlBaseURI(domElement, baseURI));
+        var extensions = [];
+        var title; // = undefined;
+
+        xmlChildElements(domElement, function (child) {
+            var nsURI = xmlNamespaceURI(child);
+            var localName = xmlLocalName(child);
+
+            if (nsURI === atomXmlNs) {
+                if (localName === "title") {
+                    if (title !== undefined) {
+                        throw { message: "Invalid AtomPub service document: collection has more than one child title element", element: child };
+                    }
+                    title = xmlInnerText(child);
+                }
+                return;
+            }
+
+            if (nsURI !== appXmlNs) {
+                extensions.push(atomReadExtensionElement(domElement));
+            }
+        });
+
+        // AtomPub (RFC 5023 Section 8.3.3) says the collection element MUST contain
+        // a title element. It's likely to be problematic if the service doc doesn't
+        // have one so here we throw.
+        if (!title) {
+            throw { message: "Invalid AtomPub service document: collection has no title element", element: domElement };
+        }
+
+        return { title: title, href: href, extensions: extensions };
+    };
+
+    var atomNewElement = function (dom, name, children) {
+        /// <summary>Creates a new DOM element in the Atom namespace.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Local name of the Atom element to create.</param>
+        /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
+        /// <returns>New DOM element in the Atom namespace.</returns>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended as a child of the new DOM Element.
+        /// </remarks>
+
+        return xmlNewElement(dom, atomXmlNs, xmlQualifiedName(atomPrefix, name), children);
+    };
+
+    var atomNewAttribute = function (dom, name, value) {
+        /// <summary>Creates a new DOM attribute for an Atom element in the default namespace.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Local name of the OData attribute to create.</param>
+        /// <param name="value">Attribute value.</param>
+        /// <returns>New DOM attribute in the default namespace.</returns>
+
+        return xmlNewAttribute(dom, null, name, value);
+    };
+
+    var atomCanRemoveProperty = function (propertyElement) {
+        /// <summary>Checks whether the property represented by domElement can be removed from the atom document DOM tree.</summary>
+        /// <param name="propertyElement">DOM element for the property to test.</param>
+        /// <remarks>
+        ///     The property can only be removed if it doens't have any children and only has namespace or type declaration attributes.
+        /// </remarks>
+        /// <returns type="Boolean">True is the property can be removed; false otherwise.</returns>
+
+        if (propertyElement.childNodes.length > 0) {
+            return false;
+        }
+
+        var isEmpty = true;
+        var attributes = propertyElement.attributes;
+        var i, len;
+        for (i = 0, len = attributes.length; i < len && isEmpty; i++) {
+            var attribute = attributes[i];
+
+            isEmpty = isEmpty && isXmlNSDeclaration(attribute) ||
+                 (xmlNamespaceURI(attribute) == odataMetaXmlNs && xmlLocalName(attribute) === "type");
+        }
+        return isEmpty;
+    };
+
+    var atomNewODataNavigationProperty = function (dom, name, kind, value, model) {
+        /// <summary>Creates a new Atom link DOM element for a navigation property in an OData Atom document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="kind" type="String">Navigation property kind. Expected values are "deferred", "entry", or "feed".</param>
+        /// <param name="value" optional="true" mayBeNull="true">Value of the navigation property, if any.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new Atom link DOM element for the navigation property and the
+        ///     required data service version for this property.
+        /// </returns>
+
+        var linkType = null;
+        var linkContent = null;
+        var linkContentBodyData = null;
+        var href = "";
+
+        if (kind !== "deferred") {
+            linkType = atomNewAttribute(dom, "type", "application/atom+xml;type=" + kind);
+            linkContent = xmlNewODataMetaElement(dom, "inline");
+
+            if (value) {
+                href = value.__metadata && value.__metadata.uri || "";
+                linkContentBodyData =
+                    atomNewODataFeed(dom, value, model) ||
+                    atomNewODataEntry(dom, value, model);
+                xmlAppendChild(linkContent, linkContentBodyData.element);
+            }
+        } else {
+            href = value.__deferred.uri;
+        }
+
+        var navProp = atomNewElement(dom, "link", [
+            atomNewAttribute(dom, "href", href),
+            atomNewAttribute(dom, "rel", normalizeURI(name, odataRelatedPrefix)),
+            linkType,
+            linkContent
+        ]);
+
+        return xmlNewODataElementInfo(navProp, linkContentBodyData ? linkContentBodyData.dsv : "1.0");
+    };
+
+    var atomNewODataEntryDataItem = function (dom, name, value, dataItemMetadata, dataItemModel, model) {
+        /// <summary>Creates a new DOM element for a data item in an entry, complex property, or collection property.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Data item name.</param>
+        /// <param name="value" optional="true" mayBeNull="true">Value of the data item, if any.</param>
+        /// <param name="dataItemMetadata" type="Object" optional="true">Object containing metadata about the data item.</param>
+        /// <param name="dataItemModel" type="Object" optional="true">Object describing the data item in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the appropriate namespace for the data item and the
+        ///     required data service version for it.
+        /// </returns>
+
+        if (isNamedStream(value)) {
+            return null;
+        }
+
+        var dataElement = xmlNewODataDataElement(dom, name, value, dataItemMetadata, dataItemModel, model);
+        if (!dataElement) {
+            // This may be a navigation property.
+            var navPropKind = navigationPropertyKind(value, dataItemModel);
+            djsassert(navPropKind !== null, "atomNewODataEntryDataItem - navigation property kind is null for property " + name);
+
+            dataElement = atomNewODataNavigationProperty(dom, name, navPropKind, value, model);
+        }
+        return dataElement;
+    };
+
+    var atomEntryCustomization = function (dom, entry, entryProperties, customization) {
+        /// <summary>Applies a feed customization by transforming an Atom entry DOM element as needed.</summary>
+        /// <param name="dom">DOM document used for creating any new DOM nodes required by the customization.</param>
+        /// <param name="entry">DOM element for the Atom entry to which the customization is going to be applied.</param>
+        /// <param name="entryProperties">DOM element containing the properties of the Atom entry.</param>
+        /// <param name="customization" type="Object">Object describing an applicable feed customization.</param>
+        /// <remarks>
+        ///     Look into the atomfeedCustomization function for a description of the customization object.
+        /// </remarks>
+        /// <returns type="String">Data service version required by the applied customization</returns>
+
+        var atomProperty = xmlFindElementByPath(entryProperties, odataXmlNs, customization.propertyPath);
+        var atomPropertyNullAttribute = atomProperty && xmlAttributeNode(atomProperty, "null", odataMetaXmlNs);
+        var atomPropertyValue;
+        var dataServiceVersion = "1.0";
+
+        if (atomPropertyNullAttribute && atomPropertyNullAttribute.value === "true") {
+            return dataServiceVersion;
+        }
+
+        if (atomProperty) {
+            atomPropertyValue = xmlInnerText(atomProperty) || "";
+            if (!customization.keepInContent) {
+                dataServiceVersion = "2.0";
+                var parent = atomProperty.parentNode;
+                var candidate = parent;
+
+                parent.removeChild(atomProperty);
+                while (candidate !== entryProperties && atomCanRemoveProperty(candidate)) {
+                    parent = candidate.parentNode;
+                    parent.removeChild(candidate);
+                    candidate = parent;
+                }
+            }
+        }
+
+        var targetNode = xmlNewNodeByPath(dom, entry,
+            customization.nsURI, customization.nsPrefix, customization.entryPath);
+
+        if (targetNode.nodeType === 2) {
+            targetNode.value = atomPropertyValue;
+            return dataServiceVersion;
+        }
+
+        var contentKind = customization.contentKind;
+        xmlAppendChildren(targetNode, [
+                contentKind && xmlNewAttribute(dom, null, "type", contentKind),
+                contentKind === "xhtml" ? xmlNewFragment(dom, atomPropertyValue) : atomPropertyValue
+        ]);
+
+        return dataServiceVersion;
+    };
+
+    var atomNewODataEntry = function (dom, data, model) {
+        /// <summary>Creates a new DOM element for an Atom entry.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="data" type="Object">Entry object in the library's internal representation.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element for the Atom entry and the required data service version for it.
+        /// </returns>
+
+        var payloadMetadata = data.__metadata || {};
+        var propertiesMetadata = payloadMetadata.properties || {};
+
+        var etag = payloadMetadata.etag;
+        var uri = payloadMetadata.uri;
+        var typeName = payloadMetadata.type;
+        var entityType = lookupEntityType(typeName, model);
+
+        var properties = xmlNewODataMetaElement(dom, "properties");
+        var entry = atomNewElement(dom, "entry", [
+            atomNewElement(dom, "author",
+                atomNewElement(dom, "name")
+            ),
+            etag && xmlNewODataMetaAttribute(dom, "etag", etag),
+            uri && atomNewElement(dom, "id", uri),
+            typeName && atomNewElement(dom, "category", [
+                atomNewAttribute(dom, "term", typeName),
+                atomNewAttribute(dom, "scheme", odataScheme)
+            ]),
+        // TODO: MLE support goes here.
+            atomNewElement(dom, "content", [
+                atomNewAttribute(dom, "type", "application/xml"),
+                properties
+            ])
+        ]);
+
+        var dataServiceVersion = "1.0";
+        for (var name in data) {
+            if (name !== "__metadata") {
+                var entryDataItemMetadata = propertiesMetadata[name] || {};
+                var entryDataItemModel = entityType && (
+                    lookupProperty(entityType.property, name) ||
+                    lookupProperty(entityType.navigationProperty, name));
+
+                var entryDataItem = atomNewODataEntryDataItem(dom, name, data[name], entryDataItemMetadata, entryDataItemModel, model);
+                if (entryDataItem) {
+                    var entryElement = entryDataItem.element;
+                    var entryElementParent = (xmlNamespaceURI(entryElement) === atomXmlNs) ? entry : properties;
+
+                    xmlAppendChild(entryElementParent, entryElement);
+                    dataServiceVersion = maxVersion(dataServiceVersion, entryDataItem.dsv);
+                }
+            }
+        }
+
+        atomApplyAllFeedCustomizations(entityType, model, function (customization) {
+            var customizationDsv = atomEntryCustomization(dom, entry, properties, customization);
+            dataServiceVersion = maxVersion(dataServiceVersion, customizationDsv);
+        });
+
+        return xmlNewODataElementInfo(entry, dataServiceVersion);
+    };
+
+    var atomNewODataFeed = function (dom, data, model) {
+        /// <summary>Creates a new DOM element for an Atom feed.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="data" type="Object">Feed object in the library's internal representation.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element for the Atom feed and the required data service version for it.
+        /// </returns>
+
+        var entries = isArray(data) ? data : data.results;
+
+        if (!entries) {
+            return null;
+        }
+
+        var dataServiceVersion = "1.0";
+        var atomFeed = atomNewElement(dom, "feed");
+
+        var i, len;
+        for (i = 0, len = entries.length; i < len; i++) {
+            var atomEntryData = atomNewODataEntry(dom, entries[i], model);
+            xmlAppendChild(atomFeed, atomEntryData.element);
+            dataServiceVersion = maxVersion(dataServiceVersion, atomEntryData.dsv);
+        }
+        return xmlNewODataElementInfo(atomFeed, dataServiceVersion);
+    };
+
+    var atomNewODataDocument = function (data, model) {
+        /// <summary>Creates a new OData Atom document.</summary>
+        /// <param name="data" type="Object">Feed or entry object in the libary's internal representaion.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM document for the Atom document and the required data service version for it.
+        /// </returns>
+
+        if (data) {
+            var atomRootWriter = isFeed(data) && atomNewODataFeed ||
+                isObject(data) && atomNewODataEntry;
+
+            if (atomRootWriter) {
+                var dom = xmlDom();
+                var atomRootData = atomRootWriter(dom, data, model);
+
+                if (atomRootData) {
+                    var atomRootElement = atomRootData.element;
+                    xmlAppendChildren(atomRootElement, [
+                        xmlNewNSDeclaration(dom, odataMetaXmlNs, odataMetaPrefix),
+                        xmlNewNSDeclaration(dom, odataXmlNs, odataPrefix)
+                    ]);
+                    return xmlNewODataElementInfo(xmlAppendChild(dom, atomRootElement), atomRootData.dsv);
+                }
+            }
+        }
+        return null;
+    };
+
+    var atomParser = function (handler, text, context) {
+        /// <summary>Parses an ATOM document (feed, entry or service document).</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="text" type="String">Document text.</param>
+        /// <param name="context" type="Object">Object with parsing context.</param>
+        /// <returns>An object representation of the document; undefined if not applicable.</returns>
+
+        if (text) {
+            var atomDoc = xmlParse(text);
+            var atomRoot = xmlFirstChildElement(atomDoc);
+            if (atomRoot) {
+                return atomReadDocument(atomRoot, null, context.metadata);
+            }
+        }
+    };
+
+    var atomSerializer = function (handler, data, context) {
+        /// <summary>Serializes an ATOM object into a document (feed or entry).</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="data" type="Object">Representation of feed or entry.</param>
+        /// <param name="context" type="Object">Object with parsing context.</param>
+        /// <returns>An text representation of the data object; undefined if not applicable.</returns>
+
+        var cType = context.contentType = context.contentType || contentType(atomMediaType);
+        if (cType && cType.mediaType === atomMediaType) {
+            var atomDoc = atomNewODataDocument(data, context.metadata);
+            if (atomDoc) {
+                context.dataServiceVersion = maxVersion(context.dataServiceVersion || "1.0", atomDoc.dsv);
+                return xmlSerialize(atomDoc.element);
+            }
+        }
+        // Allow undefined to be returned.
+    };
+
+    odata.atomHandler = handler(atomParser, atomSerializer, atomAcceptTypes.join(","), MAX_DATA_SERVICE_VERSION);
+
+    // DATAJS INTERNAL START
+    odata.atomParser = atomParser;
+    odata.atomSerializer = atomSerializer;
+    odata.atomReadDocument = atomReadDocument;
+    odata.atomReadFeed = atomReadFeed;
+    odata.atomReadFeedLink = atomReadFeedLink;
+    odata.atomReadLink = atomReadLink;
+    odata.atomReadExtensionElement = atomReadExtensionElement;
+    odata.atomReadExtensionAttributes = atomReadExtensionAttributes;
+    odata.atomReadEntry = atomReadEntry;
+    odata.atomReadEntryType = atomReadEntryType;
+    odata.atomReadEntryContent = atomReadEntryContent;
+    odata.atomReadEntryLink = atomReadEntryLink;
+    odata.atomReadEntryStructuralObject = atomReadEntryStructuralObject;
+    odata.atomReadServiceDocument = atomReadServiceDocument;
+    odata.atomReadServiceDocumentWorkspace = atomReadServiceDocumentWorkspace;
+    odata.atomReadServiceDocumentCollection = atomReadServiceDocumentCollection;
+    odata.expandedFeedCustomizationPath = expandedFeedCustomizationPath;
+    odata.lookupPropertyType = lookupPropertyType;
+    odata.atomSetEntryValueByPath = atomSetEntryValueByPath;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/odata-batch.js b/JSLib/src/odata-batch.js
new file mode 100644
index 0000000..770d875
--- /dev/null
+++ b/JSLib/src/odata-batch.js
@@ -0,0 +1,393 @@
+/// <reference path="odata-utils.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-batch.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports
+
+    var extend = datajs.extend;
+    var isArray = datajs.isArray;
+    var trimString = datajs.trimString;
+
+    var contentType = odata.contentType;
+    var handler = odata.handler;
+    var isBatch = odata.isBatch;
+    var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
+    var normalizeHeaders = odata.normalizeHeaders;
+    var payloadTypeOf = odata.payloadTypeOf;
+    var prepareRequest = odata.prepareRequest;
+
+    // CONTENT START
+    var batchMediaType = "multipart/mixed";
+    var responseStatusRegex = /^HTTP\/1\.\d (\d{3}) (.*)$/i;
+    var responseHeaderRegex = /^([^()<>@,;:\\"\/[\]?={} \t]+)\s?:\s?(.*)/;
+
+    var hex16 = function () {
+        /// <summary>
+        /// Calculates a random 16 bit number and returns it in hexadecimal format.
+        /// </summary>
+        /// <returns type="String">A 16-bit number in hex format.</returns>
+
+        return Math.floor((1 + Math.random()) * 0x10000).toString(16).substr(1);
+    };
+
+    var createBoundary = function (prefix) {
+        /// <summary>
+        /// Creates a string that can be used as a multipart request boundary.
+        /// </summary>
+        /// <param name="prefix" type="String" optional="true">String to use as the start of the boundary string</param>
+        /// <returns type="String">Boundary string of the format: <prefix><hex16>-<hex16>-<hex16></returns>
+
+        return prefix + hex16() + "-" + hex16() + "-" + hex16();
+    };
+
+    var partHandler = function (context) {
+        /// <summary>
+        /// Gets the handler for data serialization of individual requests / responses in a batch.
+        /// </summary>
+        /// <param name="context">Context used for data serialization.</param>
+        /// <returns>Handler object.</returns>
+
+        return context.handler.partHandler;
+    };
+
+    var currentBoundary = function (context) {
+        /// <summary>
+        /// Gets the current boundary used for parsing the body of a multipart response.
+        /// </summary>
+        /// <param name="context">Context used for parsing a multipart response.</param>
+        /// <returns type="String">Boundary string.</returns>
+
+        var boundaries = context.boundaries;
+        return boundaries[boundaries.length - 1];
+    };
+
+    var batchParser = function (handler, text, context) {
+        /// <summary>Parses a batch response.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="text" type="String">Batch text.</param>
+        /// <param name="context" type="Object">Object with parsing context.</param>
+        /// <returns>An object representation of the batch.</returns>
+
+        var boundary = context.contentType.properties["boundary"];
+        return { __batchResponses: readBatch(text, { boundaries: [boundary], handlerContext: context }) };
+    };
+
+    var batchSerializer = function (handler, data, context) {
+        /// <summary>Serializes a batch object representation into text.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="data" type="Object">Representation of a batch.</param>
+        /// <param name="context" type="Object">Object with parsing context.</param>
+        /// <returns>An text representation of the batch object; undefined if not applicable.</returns>
+
+        var cType = context.contentType = context.contentType || contentType(batchMediaType);
+        if (cType.mediaType === batchMediaType) {
+            return writeBatch(data, context);
+        }
+    };
+
+    var readBatch = function (text, context) {
+        /// <summary>
+        /// Parses a multipart/mixed response body from from the position defined by the context.
+        /// </summary>
+        /// <param name="text" type="String" optional="false">Body of the multipart/mixed response.</param>
+        /// <param name="context">Context used for parsing.</param>
+        /// <returns>Array of objects representing the individual responses.</returns>
+
+        var delimiter = "--" + currentBoundary(context);
+
+        // Move beyond the delimiter and read the complete batch
+        readTo(text, context, delimiter);
+
+        // Ignore the incoming line
+        readLine(text, context);
+
+        // Read the batch parts
+        var responses = [];
+        var partEnd;
+
+        while (partEnd !== "--" && context.position < text.length) {
+            var partHeaders = readHeaders(text, context);
+            var partContentType = contentType(partHeaders["Content-Type"]);
+
+            var changeResponses;
+            if (partContentType && partContentType.mediaType === batchMediaType) {
+                context.boundaries.push(partContentType.properties["boundary"]);
+                try {
+                    changeResponses = readBatch(text, context);
+                } catch (e) {
+                    e.response = readResponse(text, context, delimiter);
+                    changeResponses = [e];
+                }
+                responses.push({ __changeResponses: changeResponses });
+                context.boundaries.pop();
+                readTo(text, context, "--" + currentBoundary(context));
+            } else {
+                if (!partContentType || partContentType.mediaType !== "application/http") {
+                    throw { message: "invalid MIME part type " };
+                }
+                // Skip empty line
+                readLine(text, context);
+                // Read the response
+                var response = readResponse(text, context, delimiter);
+                try {
+                    if (response.statusCode >= 200 && response.statusCode <= 299) {
+                        partHandler(context.handlerContext).read(response, context.handlerContext);
+                    } else {
+                        // Keep track of failed responses and continue processing the batch.
+                        response = { message: "HTTP request failed", response: response };
+                    }
+                } catch (e) {
+                    response = e;
+                }
+
+                responses.push(response);
+            }
+
+            partEnd = text.substr(context.position, 2);
+
+            // Ignore the incoming line.
+            readLine(text, context);
+        }
+        return responses;
+    };
+
+    var readHeaders = function (text, context) {
+        /// <summary>
+        /// Parses the http headers in the text from the position defined by the context.
+        /// </summary>
+        /// <param name="text" type="String" optional="false">Text containing an http response's headers</param>
+        /// <param name="context">Context used for parsing.</param>
+        /// <returns>Object containing the headers as key value pairs.</returns>
+        /// <remarks>
+        /// This function doesn't support split headers and it will stop reading when it hits two consecutive line breaks.
+        /// </remarks>
+
+        var headers = {};
+        var parts;
+        var line;
+        var pos;
+
+        do {
+            pos = context.position;
+            line = readLine(text, context);
+            parts = responseHeaderRegex.exec(line);
+            if (parts !== null) {
+                headers[parts[1]] = parts[2];
+            } else {
+                // Whatever was found is not a header, so reset the context position.
+                context.position = pos;
+            }
+        } while (line && parts);
+
+        normalizeHeaders(headers);
+
+        return headers;
+    };
+
+    var readResponse = function (text, context, delimiter) {
+        /// <summary>
+        /// Parses an HTTP response.
+        /// </summary>
+        /// <param name="text" type="String" optional="false">Text representing the http response.</param>
+        /// <param name="context" optional="false">Context used for parsing.</param>
+        /// <param name="delimiter" type="String" optional="false">String used as delimiter of the multipart response parts.</param>
+        /// <returns>Object representing the http response.</returns>
+
+        // Read the status line.
+        var pos = context.position;
+        var match = responseStatusRegex.exec(readLine(text, context));
+
+        var statusCode;
+        var statusText;
+        var headers;
+
+        if (match) {
+            statusCode = match[1];
+            statusText = match[2];
+            headers = readHeaders(text, context);
+            readLine(text, context);
+        } else {
+            context.position = pos;
+        }
+
+        return {
+            statusCode: statusCode,
+            statusText: statusText,
+            headers: headers,
+            body: readTo(text, context, "\r\n" + delimiter)
+        };
+    };
+
+    var readLine = function (text, context) {
+        /// <summary>
+        /// Returns a substring from the position defined by the context up to the next line break (CRLF).
+        /// </summary>
+        /// <param name="text" type="String" optional="false">Input string.</param>
+        /// <param name="context" optional="false">Context used for reading the input string.</param>
+        /// <returns type="String">Substring to the first ocurrence of a line break or null if none can be found. </returns>
+
+        return readTo(text, context, "\r\n");
+    };
+
+    var readTo = function (text, context, str) {
+        /// <summary>
+        /// Returns a substring from the position given by the context up to value defined by the str parameter and increments the position in the context.
+        /// </summary>
+        /// <param name="text" type="String" optional="false">Input string.</param>
+        /// <param name="context" type="Object" optional="false">Context used for reading the input string.</param>
+        /// <param name="str" type="String" optional="true">Substring to read up to.</param>
+        /// <returns type="String">Substring to the first ocurrence of str or the end of the input string if str is not specified. Null if the marker is not found.</returns>
+
+        var start = context.position || 0;
+        var end = text.length;
+        if (str) {
+            end = text.indexOf(str, start);
+            if (end === -1) {
+                return null;
+            }
+            context.position = end + str.length;
+        } else {
+            context.position = end;
+        }
+
+        return text.substring(start, end);
+    };
+
+    var writeBatch = function (data, context) {
+        /// <summary>
+        /// Serializes a batch request object to a string.
+        /// </summary>
+        /// <param name="data" optional="false">Batch request object in payload representation format</param>
+        /// <param name="context" optional="false">Context used for the serialization</param>
+        /// <returns type="String">String representing the batch request</returns>
+
+        if (!isBatch(data)) {
+            throw { message: "Data is not a batch object." };
+        }
+
+        var batchBoundary = createBoundary("batch_");
+        var batchParts = data.__batchRequests;
+        var batch = "";
+        var i, len;
+        for (i = 0, len = batchParts.length; i < len; i++) {
+            batch += writeBatchPartDelimiter(batchBoundary, false) +
+                     writeBatchPart(batchParts[i], context);
+        }
+        batch += writeBatchPartDelimiter(batchBoundary, true);
+
+        // Register the boundary with the request content type.
+        var contentTypeProperties = context.contentType.properties;
+        contentTypeProperties.boundary = batchBoundary;
+
+        return batch;
+    };
+
+    var writeBatchPartDelimiter = function (boundary, close) {
+        /// <summary>
+        /// Creates the delimiter that indicates that start or end of an individual request.
+        /// </summary>
+        /// <param name="boundary" type="String" optional="false">Boundary string used to indicate the start of the request</param>
+        /// <param name="close" type="Boolean">Flag indicating that a close delimiter string should be generated</param>
+        /// <returns type="String">Delimiter string</returns>
+
+        var result = "\r\n--" + boundary;
+        if (close) {
+            result += "--";
+        }
+
+        return result + "\r\n";
+    };
+
+    var writeBatchPart = function (part, context, nested) {
+        /// <summary>
+        /// Serializes a part of a batch request to a string. A part can be either a GET request or
+        /// a change set grouping several CUD (create, update, delete) requests.
+        /// </summary>
+        /// <param name="part" optional="false">Request or change set object in payload representation format</param>
+        /// <param name="context" optional="false">Object containing context information used for the serialization</param>
+        /// <param name="nested" type="boolean" optional="true">Flag indicating that the part is nested inside a change set</param>
+        /// <returns type="String">String representing the serialized part</returns>
+        /// <remarks>
+        /// A change set is an array of request objects and they cannot be nested inside other change sets.
+        /// </remarks>
+
+        var changeSet = part.__changeRequests;
+        var result;
+        if (isArray(changeSet)) {
+            if (nested) {
+                throw { message: "Not Supported: change set nested in other change set" };
+            }
+
+            var changeSetBoundary = createBoundary("changeset_");
+            result = "Content-Type: " + batchMediaType + "; boundary=" + changeSetBoundary + "\r\n";
+            var i, len;
+            for (i = 0, len = changeSet.length; i < len; i++) {
+                result += writeBatchPartDelimiter(changeSetBoundary, false) +
+                     writeBatchPart(changeSet[i], context, true);
+            }
+
+            result += writeBatchPartDelimiter(changeSetBoundary, true);
+        } else {
+            result = "Content-Type: application/http\r\nContent-Transfer-Encoding: binary\r\n\r\n";
+            var partContext = extend({}, context);
+            partContext.handler = handler;
+            partContext.request = part;
+            partContext.contentType = null;
+
+            prepareRequest(part, partHandler(context), partContext);
+            result += writeRequest(part);
+        }
+
+        return result;
+    };
+
+    var writeRequest = function (request) {
+        /// <summary>
+        /// Serializes a request object to a string.
+        /// </summary>
+        /// <param name="request" optional="false">Request object to serialize</param>
+        /// <returns type="String">String representing the serialized request</returns>
+
+        var result = (request.method ? request.method : "GET") + " " + request.requestUri + " HTTP/1.1\r\n";
+        for (var name in request.headers) {
+            if (request.headers[name]) {
+                result = result + name + ": " + request.headers[name] + "\r\n";
+            }
+        }
+
+        result += "\r\n";
+
+        if (request.body) {
+            result += request.body;
+        }
+
+        return result;
+    };
+
+    odata.batchHandler = handler(batchParser, batchSerializer, batchMediaType, MAX_DATA_SERVICE_VERSION);
+
+    // DATAJS INTERNAL START
+    odata.batchSerializer = batchSerializer;
+    odata.writeRequest = writeRequest;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/odata-gml.js b/JSLib/src/odata-gml.js
new file mode 100644
index 0000000..2bcf5f5
--- /dev/null
+++ b/JSLib/src/odata-gml.js
@@ -0,0 +1,831 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-gml.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports.
+
+    var contains = datajs.contains;
+    var djsassert = datajs.djsassert;
+    var http = datajs.http;
+    var isArray = datajs.isArray;
+    var xmlAppendChild = datajs.xmlAppendChild;
+    var xmlAttributeValue = datajs.xmlAttributeValue;
+    var xmlChildElements = datajs.xmlChildElements;
+    var xmlFirstChildElement = datajs.xmlFirstChildElement;
+    var xmlInnerText = datajs.xmlInnerText;
+    var xmlLocalName = datajs.xmlLocalName;
+    var xmlNamespaceURI = datajs.xmlNamespaceURI;
+    var xmlNewElement = datajs.xmlNewElement;
+    var xmlQualifiedName = datajs.xmlQualifiedName;
+    var GEOJSON_POINT = odata.GEOJSON_POINT;
+    var GEOJSON_LINESTRING = odata.GEOJSON_LINESTRING;
+    var GEOJSON_POLYGON = odata.GEOJSON_POLYGON;
+    var GEOJSON_MULTIPOINT = odata.GEOJSON_MULTIPOINT;
+    var GEOJSON_MULTILINESTRING = odata.GEOJSON_MULTILINESTRING;
+    var GEOJSON_MULTIPOLYGON = odata.GEOJSON_MULTIPOLYGON;
+    var GEOJSON_GEOMETRYCOLLECTION = odata.GEOJSON_GEOMETRYCOLLECTION;
+
+    // CONTENT START
+    var gmlOpenGis = http + "www.opengis.net";           // http://www.opengis.net
+    var gmlXmlNs = gmlOpenGis + "/gml";                 // http://www.opengis.net/gml
+    var gmlSrsPrefix = gmlOpenGis + "/def/crs/EPSG/0/"; // http://www.opengis.net/def/crs/EPSG/0/
+
+    var gmlPrefix = "gml";
+
+    var gmlCreateGeoJSONOBject = function (type, member, data) {
+        /// <summary>Creates a GeoJSON object with the specified type, member and value.</summary>
+        /// <param name="type" type="String">GeoJSON object type.</param>
+        /// <param name="member" type="String">Name for the data member in the GeoJSON object.</param>
+        /// <param name="data">Data to be contained by the GeoJSON object.</param>
+        /// <returns type="Object">GeoJSON object.</returns>
+
+        var result = { type: type };
+        result[member] = data;
+        return result;
+    };
+
+    var gmlSwapLatLong = function (coordinates) {
+        /// <summary>Swaps the longitude and latitude in the coordinates array.</summary>
+        /// <param name="coordinates" type="Array">Array of doubles descrbing a set of coordinates.</param>
+        /// <returns type="Array">Array of doubles with the latitude and longitude components swapped.</returns>
+
+        if (isArray(coordinates) && coordinates.length >= 2) {
+            var tmp = coordinates[0];
+            coordinates[0] = coordinates[1];
+            coordinates[1] = tmp;
+        }
+        return coordinates;
+    };
+
+    var gmlReadODataMultiItem = function (domElement, type, member, members, valueReader, isGeography) {
+        /// <summary>
+        ///    Reads a GML DOM element that represents a composite structure like a multi-point or a
+        ///    multi-geometry returnig its GeoJSON representation.
+        /// </summary>
+        /// <param name="domElement">GML DOM element.</param>
+        /// <param name="type" type="String">GeoJSON object type.</param>
+        /// <param name="member" type="String">Name for the child element representing a single item in the composite structure.</param>
+        /// <param name="members" type="String">Name for the child element representing a collection of items in the composite structure.</param>
+        /// <param name="valueReader" type="Function">Callback function invoked to get the coordinates of each item in the comoposite structure.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">GeoJSON object.</returns>
+
+        var coordinates = gmlReadODataMultiItemValue(domElement, member, members, valueReader, isGeography);
+        return gmlCreateGeoJSONOBject(type, "coordinates", coordinates);
+    };
+
+    var gmlReadODataMultiItemValue = function (domElement, member, members, valueReader, isGeography) {
+        /// <summary>
+        ///    Reads the value of a GML DOM element that represents a composite structure like a multi-point or a
+        ///    multi-geometry returnig its items.
+        /// </summary>
+        /// <param name="domElement">GML DOM element.</param>
+        /// <param name="type" type="String">GeoJSON object type.</param>
+        /// <param name="member" type="String">Name for the child element representing a single item in the composite structure.</param>
+        /// <param name="members" type="String">Name for the child element representing a collection of items in the composite structure.</param>
+        /// <param name="valueReader" type="Function">Callback function invoked to get the transformed value of each item in the comoposite structure.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array containing the transformed value of each item in the multi-item.</returns>
+
+        var items = [];
+
+        xmlChildElements(domElement, function (child) {
+            if (xmlNamespaceURI(child) !== gmlXmlNs) {
+                return;
+            }
+
+            var localName = xmlLocalName(child);
+
+            if (localName === member) {
+                var valueElement = xmlFirstChildElement(child, gmlXmlNs);
+                if (valueElement) {
+                    var value = valueReader(valueElement, isGeography);
+                    if (value) {
+                        items.push(value);
+                    }
+                }
+                return;
+            }
+
+            if (localName === members) {
+                xmlChildElements(child, function (valueElement) {
+                    if (xmlNamespaceURI(valueElement) !== gmlXmlNs) {
+                        return;
+                    }
+
+                    var value = valueReader(valueElement, isGeography);
+                    if (value) {
+                        items.push(value);
+                    }
+                });
+            }
+        });
+        return items;
+    };
+
+    var gmlReadODataCollection = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a multi-geometry returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">MultiGeometry object in GeoJSON format.</returns>
+
+        var geometries = gmlReadODataMultiItemValue(domElement, "geometryMember", "geometryMembers", gmlReadODataSpatialValue, isGeography);
+        return gmlCreateGeoJSONOBject(GEOJSON_GEOMETRYCOLLECTION, "geometries", geometries);
+    };
+
+    var gmlReadODataLineString = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a line string returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">LineString object in GeoJSON format.</returns>
+
+        return gmlCreateGeoJSONOBject(GEOJSON_LINESTRING, "coordinates", gmlReadODataLineValue(domElement, isGeography));
+    };
+
+    var gmlReadODataMultiLineString = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a multi-line string returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">MultiLineString object in GeoJSON format.</returns>
+
+        return gmlReadODataMultiItem(domElement, GEOJSON_MULTILINESTRING, "curveMember", "curveMembers", gmlReadODataLineValue, isGeography);
+    };
+
+    var gmlReadODataMultiPoint = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a multi-point returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">MultiPoint object in GeoJSON format.</returns>
+
+        return gmlReadODataMultiItem(domElement, GEOJSON_MULTIPOINT, "pointMember", "pointMembers", gmlReadODataPointValue, isGeography);
+    };
+
+    var gmlReadODataMultiPolygon = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a multi-polygon returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">MultiPolygon object in GeoJSON format.</returns>
+
+        return gmlReadODataMultiItem(domElement, GEOJSON_MULTIPOLYGON, "surfaceMember", "surfaceMembers", gmlReadODataPolygonValue, isGeography);
+    };
+
+    var gmlReadODataPoint = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a point returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">Point object in GeoJSON format.</returns>
+
+        return gmlCreateGeoJSONOBject(GEOJSON_POINT, "coordinates", gmlReadODataPointValue(domElement, isGeography));
+    };
+
+    var gmlReadODataPolygon = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a polygon returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">Polygon object in GeoJSON format.</returns>
+
+        return gmlCreateGeoJSONOBject(GEOJSON_POLYGON, "coordinates", gmlReadODataPolygonValue(domElement, isGeography));
+    };
+
+    var gmlReadODataLineValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element representing a line returning its set of coordinates.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array containing an array of doubles for each coordinate of the line.</returns>
+
+        var coordinates = [];
+
+        xmlChildElements(domElement, function (child) {
+            var nsURI = xmlNamespaceURI(child);
+
+            if (nsURI !== gmlXmlNs) {
+                return;
+            }
+
+            var localName = xmlLocalName(child);
+
+            if (localName === "posList") {
+                coordinates = gmlReadODataPosListValue(child, isGeography);
+                return;
+            }
+            if (localName === "pointProperty") {
+                coordinates.push(gmlReadODataPointWrapperValue(child, isGeography));
+                return;
+            }
+            if (localName === "pos") {
+                coordinates.push(gmlReadODataPosValue(child, isGeography));
+                return;
+            }
+        });
+
+        return coordinates;
+    };
+
+    var gmlReadODataPointValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element representing a point returning its coordinates.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array of doubles containing the point coordinates.</returns>
+
+        var pos = xmlFirstChildElement(domElement, gmlXmlNs, "pos");
+        return pos ? gmlReadODataPosValue(pos, isGeography) : [];
+    };
+
+    var gmlReadODataPointWrapperValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element wrapping an element representing a point returning its coordinates.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array of doubles containing the point coordinates.</returns>
+
+        var point = xmlFirstChildElement(domElement, gmlXmlNs, "Point");
+        return point ? gmlReadODataPointValue(point, isGeography) : [];
+    };
+
+    var gmlReadODataPolygonValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element representing a polygon returning its set of coordinates.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array containing an array of array of doubles for each ring of the polygon.</returns>
+
+        var coordinates = [];
+        var exteriorFound = false;
+        xmlChildElements(domElement, function (child) {
+            if (xmlNamespaceURI(child) !== gmlXmlNs) {
+                return;
+            }
+
+            // Only the exterior and the interior rings are interesting
+            var localName = xmlLocalName(child);
+            if (localName === "exterior") {
+                exteriorFound = true;
+                coordinates.unshift(gmlReadODataPolygonRingValue(child, isGeography));
+                return;
+            }
+            if (localName === "interior") {
+                coordinates.push(gmlReadODataPolygonRingValue(child, isGeography));
+                return;
+            }
+        });
+
+        if (!exteriorFound && coordinates.length > 0) {
+            // Push an empty exterior ring.
+            coordinates.unshift([[]]);
+        }
+
+        return coordinates;
+    };
+
+    var gmlReadODataPolygonRingValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element representing a linear ring in a GML Polygon element.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array containing an array of doubles for each coordinate of the linear ring.</returns>
+
+        var value = [];
+        xmlChildElements(domElement, function (child) {
+            if (xmlNamespaceURI(child) !== gmlXmlNs || xmlLocalName(child) !== "LinearRing") {
+                return;
+            }
+            value = gmlReadODataLineValue(child, isGeography);
+        });
+        return value;
+    };
+
+    var gmlReadODataPosListValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element representing a list of positions retruning its set of coordinates.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        ///
+        ///    The positions described by the list are assumed to be 2D, so 
+        ///    an exception will be thrown if the list has an odd number elements.
+        /// </remarks>
+        /// <returns type="Array">Array containing an array of doubles for each coordinate in the list.</returns>
+
+        var coordinates = gmlReadODataPosValue(domElement, false);
+        var len = coordinates.length;
+
+        if (len % 2 !== 0) {
+            throw { message: "GML posList element has an uneven number of numeric values" };
+        }
+
+        var value = [];
+        for (var i = 0; i < len; i += 2) {
+            var pos = coordinates.slice(i, i + 2);
+            value.push(isGeography ? gmlSwapLatLong(pos) : pos);
+        }
+        return value;
+    };
+
+    var gmlReadODataPosValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML element describing a position or a set of coordinates in an OData spatial property value.</summary>
+        /// <param name="property">DOM element for the GML element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array of doubles containing the coordinates.</returns>
+
+        var value = [];
+        var delims = " \t\r\n";
+        var text = xmlInnerText(domElement);
+
+        if (text) {
+            var len = text.length;
+            var start = 0;
+            var end = 0;
+
+            while (end <= len) {
+                if (delims.indexOf(text.charAt(end)) !== -1) {
+                    var coord = text.substring(start, end);
+                    if (coord) {
+                        value.push(parseFloat(coord));
+                    }
+                    start = end + 1;
+                }
+                end++;
+            }
+        }
+
+        return isGeography ? gmlSwapLatLong(value) : value;
+    };
+
+    var gmlReadODataSpatialValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element a spatial value in an OData XML document.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each position coordinates in the resulting GeoJSON object.
+        /// </remarks>
+        /// <returns type="Array">Array containing an array of doubles for each coordinate of the polygon.</returns>
+
+        var localName = xmlLocalName(domElement);
+        var reader;
+
+        switch (localName) {
+            case "Point":
+                reader = gmlReadODataPoint;
+                break;
+            case "Polygon":
+                reader = gmlReadODataPolygon;
+                break;
+            case "LineString":
+                reader = gmlReadODataLineString;
+                break;
+            case "MultiPoint":
+                reader = gmlReadODataMultiPoint;
+                break;
+            case "MultiCurve":
+                reader = gmlReadODataMultiLineString;
+                break;
+            case "MultiSurface":
+                reader = gmlReadODataMultiPolygon;
+                break;
+            case "MultiGeometry":
+                reader = gmlReadODataCollection;
+                break;
+            default:
+                throw { message: "Unsupported element: " + localName, element: domElement };
+        }
+
+        var value = reader(domElement, isGeography);
+        // Read the CRS
+        // WCF Data Services qualifies the srsName attribute withing the GML namespace; however
+        // other end points might no do this as per the standard.
+
+        var srsName = xmlAttributeValue(domElement, "srsName", gmlXmlNs) ||
+                      xmlAttributeValue(domElement, "srsName");
+
+        if (srsName) {
+            if (srsName.indexOf(gmlSrsPrefix) !== 0) {
+                throw { message: "Unsupported srs name: " + srsName, element: domElement };
+            }
+
+            var crsId = srsName.substring(gmlSrsPrefix.length);
+            if (crsId) {
+                value.crs = {
+                    type: "name",
+                    properties: {
+                        name: "EPSG:" + crsId
+                    }
+                };
+            }
+        }
+        return value;
+    };
+
+    var gmlNewODataSpatialValue = function (dom, value, type, isGeography) {
+        /// <summary>Creates a new GML DOM element for the value of an OData spatial property or GeoJSON object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">Spatial property value in GeoJSON format.</param>
+        /// <param name="type" type="String">String indicating the GeoJSON type of the value to serialize.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the spatial value. </returns>
+
+        var gmlWriter;
+
+        switch (type) {
+            case GEOJSON_POINT:
+                gmlWriter = gmlNewODataPoint;
+                break;
+            case GEOJSON_LINESTRING:
+                gmlWriter = gmlNewODataLineString;
+                break;
+            case GEOJSON_POLYGON:
+                gmlWriter = gmlNewODataPolygon;
+                break;
+            case GEOJSON_MULTIPOINT:
+                gmlWriter = gmlNewODataMultiPoint;
+                break;
+            case GEOJSON_MULTILINESTRING:
+                gmlWriter = gmlNewODataMultiLineString;
+                break;
+            case GEOJSON_MULTIPOLYGON:
+                gmlWriter = gmlNewODataMultiPolygon;
+                break;
+            case GEOJSON_GEOMETRYCOLLECTION:
+                gmlWriter = gmlNewODataGeometryCollection;
+                break;
+            default:
+                djsassert(false, "gmlNewODataSpatialValue - Unknown GeoJSON type <" + type + ">!!");
+                return null;
+        }
+
+        var gml = gmlWriter(dom, value, isGeography);
+
+        // Set the srsName attribute if applicable.
+        var crs = value.crs;
+        if (crs) {
+            if (crs.type === "name") {
+                var properties = crs.properties;
+                var name = properties && properties.name;
+                if (name && name.indexOf("ESPG:") === 0 && name.length > 5) {
+                    var crsId = name.substring(5);
+                    var srsName = xmlNewAttribute(dom, null, "srsName", gmlPrefix + crsId);
+                    xmlAppendChild(gml, srsName);
+                }
+            }
+        }
+
+        return gml;
+    };
+
+    var gmlNewODataElement = function (dom, name, children) {
+        /// <summary>Creates a new DOM element in the GML namespace.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Local name of the GML element to create.</param>
+        /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
+        /// <returns>New DOM element in the GML namespace.</returns>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended as a child of the new DOM Element.
+        /// </remarks>
+
+        return xmlNewElement(dom, gmlXmlNs, xmlQualifiedName(gmlPrefix, name), children);
+    };
+
+    var gmlNewODataPosElement = function (dom, coordinates, isGeography) {
+        /// <summary>Creates a new GML pos DOM element.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="coordinates" type="Array">Array of doubles describing the coordinates of the pos element.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the coordinates use a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first coordinate is the Longitude and
+        ///    will be serialized as the second component of the <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New pos DOM element in the GML namespace.</returns>
+
+        var posValue = isArray(coordinates) ? coordinates : [];
+
+        // If using a geographic reference system, then the first coordinate is the longitude and it has to
+        // swapped with the latitude.
+        posValue = isGeography ? gmlSwapLatLong(posValue) : posValue;
+
+        return gmlNewODataElement(dom, "pos", posValue.join(" "));
+    };
+
+    var gmlNewODataLineElement = function (dom, name, coordinates, isGeography) {
+        /// <summary>Creates a new GML DOM element representing a line.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Name of the element to create.</param>
+        /// <param name="coordinates" type="Array">Array of array of doubles describing the coordinates of the line element.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the coordinates use a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace.</returns>
+
+        var element = gmlNewODataElement(dom, name);
+        if (isArray(coordinates)) {
+            var i, len;
+            for (i = 0, len = coordinates.length; i < len; i++) {
+                xmlAppendChild(element, gmlNewODataPosElement(dom, coordinates[i], isGeography));
+            }
+
+            if (len === 0) {
+                xmlAppendChild(element, gmlNewODataElement(dom, "posList"));
+            }
+        }
+        return element;
+    };
+
+    var gmlNewODataPointElement = function (dom, coordinates, isGeography) {
+        /// <summary>Creates a new GML Point DOM element.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON Point object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON Point.</returns>
+
+        return gmlNewODataElement(dom, "Point", gmlNewODataPosElement(dom, coordinates, isGeography));
+    };
+
+    var gmlNewODataLineStringElement = function (dom, coordinates, isGeography) {
+        /// <summary>Creates a new GML LineString DOM element.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="coordinates" type="Array">Array of array of doubles describing the coordinates of the line element.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON LineString.</returns>
+
+        return gmlNewODataLineElement(dom, "LineString", coordinates, isGeography);
+    };
+
+    var gmlNewODataPolygonRingElement = function (dom, name, coordinates, isGeography) {
+        /// <summary>Creates a new GML DOM element representing a polygon ring.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Name of the element to create.</param>
+        /// <param name="coordinates" type="Array">Array of array of doubles describing the coordinates of the polygon ring.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the coordinates use a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace.</returns>
+
+        var ringElement = gmlNewODataElement(dom, name);
+        if (isArray(coordinates) && coordinates.length > 0) {
+            var linearRing = gmlNewODataLineElement(dom, "LinearRing", coordinates, isGeography);
+            xmlAppendChild(ringElement, linearRing);
+        }
+        return ringElement;
+    };
+
+    var gmlNewODataPolygonElement = function (dom, coordinates, isGeography) {
+        /// <summary>Creates a new GML Polygon DOM element for a GeoJSON Polygon object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="coordinates" type="Array">Array of array of array of doubles describing the coordinates of the polygon.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace.</returns>
+
+        var len = coordinates && coordinates.length;
+        var element = gmlNewODataElement(dom, "Polygon");
+
+        if (isArray(coordinates) && len > 0) {
+            xmlAppendChild(element, gmlNewODataPolygonRingElement(dom, "exterior", coordinates[0], isGeography));
+
+            var i;
+            for (i = 1; i < len; i++) {
+                xmlAppendChild(element, gmlNewODataPolygonRingElement(dom, "interior", coordinates[i], isGeography));
+            }
+        }
+        return element;
+    };
+
+    var gmlNewODataPoint = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML Point DOM element for a GeoJSON Point object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON Point object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON Point.</returns>
+
+        return gmlNewODataPointElement(dom, value.coordinates, isGeography);
+    };
+
+    var gmlNewODataLineString = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML LineString DOM element for a GeoJSON LineString object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON LineString object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON LineString.</returns>
+
+        return gmlNewODataLineStringElement(dom, value.coordinates, isGeography);
+    };
+
+    var gmlNewODataPolygon = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML Polygon DOM element for a GeoJSON Polygon object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON Polygon object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON Polygon.</returns>
+
+        return gmlNewODataPolygonElement(dom, value.coordinates, isGeography);
+    };
+
+    var gmlNewODataMultiItem = function (dom, name, members, items, itemWriter, isGeography) {
+        /// <summary>Creates a new GML DOM element for a composite structure like a multi-point or a multi-geometry.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Name of the element to create.</param>
+        /// <param name="items" type="Array">Array of items in the composite structure.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the multi-item uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each of the items is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace.</returns>
+
+        var len = items && items.length;
+        var element = gmlNewODataElement(dom, name);
+
+        if (isArray(items) && len > 0) {
+            var membersElement = gmlNewODataElement(dom, members);
+            var i;
+            for (i = 0; i < len; i++) {
+                xmlAppendChild(membersElement, itemWriter(dom, items[i], isGeography));
+            }
+            xmlAppendChild(element, membersElement);
+        }
+        return element;
+    };
+
+    var gmlNewODataMultiPoint = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML MultiPoint DOM element for a GeoJSON MultiPoint object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON MultiPoint object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON MultiPoint.</returns>
+
+        return gmlNewODataMultiItem(dom, "MultiPoint", "pointMembers", value.coordinates, gmlNewODataPointElement, isGeography);
+    };
+
+    var gmlNewODataMultiLineString = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML MultiCurve DOM element for a GeoJSON MultiLineString object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON MultiLineString object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON MultiLineString.</returns>
+
+        return gmlNewODataMultiItem(dom, "MultiCurve", "curveMembers", value.coordinates, gmlNewODataLineStringElement, isGeography);
+    };
+
+    var gmlNewODataMultiPolygon = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML MultiSurface DOM element for a GeoJSON MultiPolygon object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON MultiPolygon object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON MultiPolygon.</returns>
+
+        return gmlNewODataMultiItem(dom, "MultiSurface", "surfaceMembers", value.coordinates, gmlNewODataPolygonElement, isGeography);
+    };
+
+    var gmlNewODataGeometryCollectionItem = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML element for an item in a geometry collection object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="item" type="Object">GeoJSON object in the geometry collection.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace.</returns>
+
+        return gmlNewODataSpatialValue(dom, value, value.type, isGeography);
+    };
+
+    var gmlNewODataGeometryCollection = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML MultiGeometry DOM element for a GeoJSON GeometryCollection object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON GeometryCollection object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON GeometryCollection.</returns>
+
+        return gmlNewODataMultiItem(dom, "MultiGeometry", "geometryMembers", value.geometries, gmlNewODataGeometryCollectionItem, isGeography);
+    };
+
+    // DATAJS INTERNAL START
+    odata.gmlNewODataSpatialValue = gmlNewODataSpatialValue;
+    odata.gmlReadODataSpatialValue = gmlReadODataSpatialValue;
+    odata.gmlXmlNs = gmlXmlNs;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/odata-handler.js b/JSLib/src/odata-handler.js
new file mode 100644
index 0000000..5f83429
--- /dev/null
+++ b/JSLib/src/odata-handler.js
@@ -0,0 +1,276 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-handler.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports.
+    var assigned = datajs.assigned;
+    var extend = datajs.extend;
+    var trimString = datajs.trimString;
+
+    var maxVersion = odata.maxVersion;
+
+    // CONTENT START
+
+    var MAX_DATA_SERVICE_VERSION = "3.0";
+
+    var contentType = function (str) {
+        /// <summary>Parses a string into an object with media type and properties.</summary>
+        /// <param name="str" type="String">String with media type to parse.</param>
+        /// <returns>null if the string is empty; an object with 'mediaType' and a 'properties' dictionary otherwise.</returns>
+
+        if (!str) {
+            return null;
+        }
+
+        var contentTypeParts = str.split(";");
+        var properties = {};
+
+        var i, len;
+        for (i = 1, len = contentTypeParts.length; i < len; i++) {
+            var contentTypeParams = contentTypeParts[i].split("=");
+            properties[trimString(contentTypeParams[0])] = contentTypeParams[1];
+        }
+
+        return { mediaType: trimString(contentTypeParts[0]), properties: properties };
+    };
+
+    var contentTypeToString = function (contentType) {
+        /// <summary>Serializes an object with media type and properties dictionary into a string.</summary>
+        /// <param name="contentType">Object with media type and properties dictionary to serialize.</param>
+        /// <returns>String representation of the media type object; undefined if contentType is null or undefined.</returns>
+
+        if (!contentType) {
+            return undefined;
+        }
+
+        var result = contentType.mediaType;
+        var property;
+        for (property in contentType.properties) {
+            result += ";" + property + "=" + contentType.properties[property];
+        }
+        return result;
+    };
+
+    var createReadWriteContext = function (contentType, dataServiceVersion, context, handler) {
+        /// <summary>Creates an object that is going to be used as the context for the handler's parser and serializer.</summary>
+        /// <param name="contentType">Object with media type and properties dictionary.</param>
+        /// <param name="dataServiceVersion" type="String">String indicating the version of the protocol to use.</param>
+        /// <param name="context">Operation context.</param>
+        /// <param name="handler">Handler object that is processing a resquest or response.</param>
+        /// <returns>Context object.</returns>
+
+        var rwContext = {};
+        extend(rwContext, context);
+        extend(rwContext, {
+            contentType: contentType,
+            dataServiceVersion: dataServiceVersion,
+            handler: handler
+        });
+
+        return rwContext;
+    };
+
+    var fixRequestHeader = function (request, name, value) {
+        /// <summary>Sets a request header's value. If the header has already a value other than undefined, null or empty string, then this method does nothing.</summary>
+        /// <param name="request">Request object on which the header will be set.</param>
+        /// <param name="name" type="String">Header name.</param>
+        /// <param name="value" type="String">Header value.</param>
+        if (!request) {
+            return;
+        }
+
+        var headers = request.headers;
+        if (!headers[name]) {
+            headers[name] = value;
+        }
+    };
+
+    var fixDataServiceVersionHeader = function (request, version) {
+        /// <summary>Sets the DataServiceVersion header of the request if its value is not yet defined or of a lower version.</summary>
+        /// <param name="request">Request object on which the header will be set.</param>
+        /// <param name="version" type="String">Version value.</param>
+        /// <remarks>
+        /// If the request has already a version value higher than the one supplied the this function does nothing.
+        /// </remarks>
+
+        if (request) {
+            var headers = request.headers;
+            var dsv = headers["DataServiceVersion"];
+            headers["DataServiceVersion"] = dsv ? maxVersion(dsv, version) : version;
+        }
+    };
+
+    var getRequestOrResponseHeader = function (requestOrResponse, name) {
+        /// <summary>Gets the value of a request or response header.</summary>
+        /// <param name="requestOrResponse">Object representing a request or a response.</param>
+        /// <param name="name" type="String">Name of the header to retrieve.</param>
+        /// <returns type="String">String value of the header; undefined if the header cannot be found.</returns>
+
+        var headers = requestOrResponse.headers;
+        return (headers && headers[name]) || undefined;
+    };
+
+    var getContentType = function (requestOrResponse) {
+        /// <summary>Gets the value of the Content-Type header from a request or response.</summary>
+        /// <param name="requestOrResponse">Object representing a request or a response.</param>
+        /// <returns type="Object">Object with 'mediaType' and a 'properties' dictionary; null in case that the header is not found or doesn't have a value.</returns>
+
+        return contentType(getRequestOrResponseHeader(requestOrResponse, "Content-Type"));
+    };
+
+    var versionRE = /^\s?(\d+\.\d+);?.*$/;
+    var getDataServiceVersion = function (requestOrResponse) {
+        /// <summary>Gets the value of the DataServiceVersion header from a request or response.</summary>
+        /// <param name="requestOrResponse">Object representing a request or a response.</param>
+        /// <returns type="String">Data service version; undefined if the header cannot be found.</returns>
+
+        var value = getRequestOrResponseHeader(requestOrResponse, "DataServiceVersion");
+        if (value) {
+            var matches = versionRE.exec(value);
+            if (matches && matches.length) {
+                return matches[1];
+            }
+        }
+
+        // Fall through and return undefined.
+    };
+
+    var handlerAccepts = function (handler, cType) {
+        /// <summary>Checks that a handler can process a particular mime type.</summary>
+        /// <param name="handler">Handler object that is processing a resquest or response.</param>
+        /// <param name="cType">Object with 'mediaType' and a 'properties' dictionary.</param>
+        /// <returns type="Boolean">True if the handler can process the mime type; false otherwise.</returns>
+
+        // The following check isn't as strict because if cType.mediaType = application/; it will match an accept value of "application/xml";
+        // however in practice we don't not expect to see such "suffixed" mimeTypes for the handlers.
+        return handler.accept.indexOf(cType.mediaType) >= 0;
+    };
+
+    var handlerRead = function (handler, parseCallback, response, context) {
+        /// <summary>Invokes the parser associated with a handler for reading the payload of a HTTP response.</summary>
+        /// <param name="handler">Handler object that is processing the response.</param>
+        /// <param name="parseCallback" type="Function">Parser function that will process the response payload.</param>
+        /// <param name="response">HTTP response whose payload is going to be processed.</param>
+        /// <param name="context">Object used as the context for processing the response.</param>
+        /// <returns type="Boolean">True if the handler processed the response payload and the response.data property was set; false otherwise.</returns>
+
+        if (!response || !response.headers) {
+            return false;
+        }
+
+        var cType = getContentType(response);
+        var version = getDataServiceVersion(response) || "";
+        var body = response.body;
+
+        if (!assigned(body)) {
+            return false;
+        }
+
+        if (handlerAccepts(handler, cType)) {
+            var readContext = createReadWriteContext(cType, version, context, handler);
+            readContext.response = response;
+            response.data = parseCallback(handler, body, readContext);
+            return response.data !== undefined;
+        }
+
+        return false;
+    };
+
+    var handlerWrite = function (handler, serializeCallback, request, context) {
+        /// <summary>Invokes the serializer associated with a handler for generating the payload of a HTTP request.</summary>
+        /// <param name="handler">Handler object that is processing the request.</param>
+        /// <param name="serializeCallback" type="Function">Serializer function that will generate the request payload.</param>
+        /// <param name="response">HTTP request whose payload is going to be generated.</param>
+        /// <param name="context">Object used as the context for serializing the request.</param>
+        /// <returns type="Boolean">True if the handler serialized the request payload and the request.body property was set; false otherwise.</returns>
+        if (!request || !request.headers) {
+            return false;
+        }
+
+        var cType = getContentType(request);
+        var version = getDataServiceVersion(request);
+
+        if (!cType || handlerAccepts(handler, cType)) {
+            var writeContext = createReadWriteContext(cType, version, context, handler);
+            writeContext.request = request;
+
+            request.body = serializeCallback(handler, request.data, writeContext);
+
+            if (request.body !== undefined) {
+                fixDataServiceVersionHeader(request, writeContext.dataServiceVersion || "1.0");
+
+                fixRequestHeader(request, "Content-Type", contentTypeToString(writeContext.contentType));
+                fixRequestHeader(request, "MaxDataServiceVersion", handler.maxDataServiceVersion);
+                return true;
+            }
+        }
+
+        return false;
+    };
+
+    var handler = function (parseCallback, serializeCallback, accept, maxDataServiceVersion) {
+        /// <summary>Creates a handler object for processing HTTP requests and responses.</summary>
+        /// <param name="parseCallback" type="Function">Parser function that will process the response payload.</param>
+        /// <param name="serializeCallback" type="Function">Serializer function that will generate the request payload.</param>
+        /// <param name="accept" type="String">String containing a comma separated list of the mime types that this handler can work with.</param>
+        /// <param name="maxDataServiceVersion" type="String">String indicating the highest version of the protocol that this handler can work with.</param>
+        /// <returns type="Object">Handler object.</returns>
+
+        return {
+            accept: accept,
+            maxDataServiceVersion: maxDataServiceVersion,
+
+            read: function (response, context) {
+                return handlerRead(this, parseCallback, response, context);
+            },
+
+            write: function (request, context) {
+                return handlerWrite(this, serializeCallback, request, context);
+            }
+        };
+    };
+
+    var textParse = function (handler, body /*, context */) {
+        return body;
+    };
+
+    var textSerialize = function (handler, data /*, context */) {
+        if (assigned(data)) {
+            return data.toString();
+        } else {
+            return undefined;
+        }
+    };
+
+    odata.textHandler = handler(textParse, textSerialize, "text/plain", MAX_DATA_SERVICE_VERSION);
+
+    // DATAJS INTERNAL START
+    odata.contentType = contentType;
+    odata.contentTypeToString = contentTypeToString;
+    odata.handler = handler;
+    odata.createReadWriteContext = createReadWriteContext;
+    odata.fixRequestHeader = fixRequestHeader;
+    odata.getRequestOrResponseHeader = getRequestOrResponseHeader;
+    odata.getContentType = getContentType;
+    odata.getDataServiceVersion = getDataServiceVersion;
+    odata.MAX_DATA_SERVICE_VERSION = MAX_DATA_SERVICE_VERSION;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/odata-json-light.js b/JSLib/src/odata-json-light.js
new file mode 100644
index 0000000..9a10601
--- /dev/null
+++ b/JSLib/src/odata-json-light.js
@@ -0,0 +1,1391 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-json-light.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports
+
+    var assigned = datajs.assigned;
+    var djsassert = datajs.djsassert;
+    var extend = datajs.extend;
+    var getURIInfo = datajs.getURIInfo;
+    var isArray = datajs.isArray;
+    var isDate = datajs.isDate;
+    var normalizeURI = datajs.normalizeURI;
+    var renameProperty = datajs.renameProperty;
+    var undefinedDefault = datajs.undefinedDefault;
+    var convertByteArrayToHexString = datajs.convertByteArrayToHexString;
+
+    var dataItemTypeName = odata.dataItemTypeName;
+    var EDM_DATETIME = odata.EDM_DATETIME;
+    var EDM_DATETIMEOFFSET = odata.EDM_DATETIMEOFFSET;
+    var EDM_TIME = odata.EDM_TIME;
+    var getCollectionType = odata.getCollectionType;
+    var isCollection = odata.isCollection;
+    var isCollectionType = odata.isCollectionType;
+    var isComplex = odata.isComplex;
+    var isDeferred = odata.isDeferred;
+    var isFeed = odata.isFeed;
+    var isEntry = odata.isEntry;
+    var isGeographyEdmType = odata.isGeographyEdmType;
+    var isGeometryEdmType = odata.isGeometryEdmType;
+    var isPrimitiveEdmType = odata.isPrimitiveEdmType;
+    var isPrimitive = odata.isPrimitive;
+    var lookupComplexType = odata.lookupComplexType;
+    var lookupDefaultEntityContainer = odata.lookupDefaultEntityContainer;
+    var lookupEntityContainer = odata.lookupEntityContainer;
+    var lookupEntitySet = odata.lookupEntitySet;
+    var lookupEntityType = odata.lookupEntityType;
+    var lookupFunctionImport = odata.lookupFunctionImport;
+    var lookupNavigationPropertyType = odata.lookupNavigationPropertyType;
+    var getEntitySetInfo = odata.getEntitySetInfo;
+    var lookupNavigationPropertyEntitySet = odata.lookupNavigationPropertyEntitySet;
+    var lookupProperty = odata.lookupProperty;
+    var parseBool = odata.parseBool;
+    var parseDateTime = odata.parseDateTime;
+    var parseDateTimeOffset = odata.parseDateTimeOffset;
+    var parseDuration = odata.parseDuration;
+
+    // CONTENT START
+
+    var PAYLOADTYPE_OBJECT = "o";
+    var PAYLOADTYPE_FEED = "f";
+    var PAYLOADTYPE_PRIMITIVE = "p";
+    var PAYLOADTYPE_COLLECTION = "c";
+    var PAYLOADTYPE_SVCDOC = "s";
+    var PAYLOADTYPE_LINKS = "l";
+
+    var odataNs = "odata";
+    var odataAnnotationPrefix = odataNs + ".";
+
+    var bindAnnotation = "@" + odataAnnotationPrefix + "bind";
+    var metadataAnnotation = odataAnnotationPrefix + "metadata";
+    var navUrlAnnotation = odataAnnotationPrefix + "navigationLinkUrl";
+    var typeAnnotation = odataAnnotationPrefix + "type";
+
+    var jsonLightNameMap = {
+        readLink: "self",
+        editLink: "edit",
+        nextLink: "__next",
+        mediaReadLink: "media_src",
+        mediaEditLink: "edit_media",
+        mediaContentType: "content_type",
+        mediaETag: "media_etag",
+        count: "__count",
+        media_src: "mediaReadLink",
+        edit_media: "mediaEditLink",
+        content_type: "mediaContentType",
+        media_etag: "mediaETag",
+        url: "uri"
+    };
+
+    var jsonLightAnnotations = {
+        metadata: "odata.metadata",
+        count: "odata.count",
+        next: "odata.nextLink",
+        id: "odata.id",
+        etag: "odata.etag",
+        read: "odata.readLink",
+        edit: "odata.editLink",
+        mediaRead: "odata.mediaReadLink",
+        mediaEdit: "odata.mediaEditLink",
+        mediaEtag: "odata.mediaETag",
+        mediaContentType: "odata.mediaContentType",
+        actions: "odata.actions",
+        functions: "odata.functions",
+        navigationUrl: "odata.navigationLinkUrl",
+        associationUrl: "odata.associationLinkUrl",
+        type: "odata.type"
+    };
+
+    var jsonLightAnnotationInfo = function (annotation) {
+        /// <summary>Gets the name and target of an annotation in a JSON light payload.</summary>
+        /// <param name="annotation" type="String">JSON light payload annotation.</param>
+        /// <returns type="Object">Object containing the annotation name and the target property name.</param>
+
+        if (annotation.indexOf(".") > 0) {
+            var targetEnd = annotation.indexOf("@");
+            var target = targetEnd > -1 ? annotation.substring(0, targetEnd) : null;
+            var name = annotation.substring(targetEnd + 1);
+
+            return {
+                target: target,
+                name: name,
+                isOData: name.indexOf(odataAnnotationPrefix) === 0
+            };
+        }
+        return null;
+    };
+
+    var jsonLightDataItemType = function (name, value, container, dataItemModel, model) {
+        /// <summary>Gets the type name of a JSON light data item that belongs to a feed, an entry, a complex type property, or a collection property.</summary>
+        /// <param name="name" type="String">Name of the data item for which the type name is going to be retrieved.</param>
+        /// <param name="value">Value of the data item.</param>
+        /// <param name="container" type="Object">JSON light object that owns the data item.</param>
+        /// <param name="dataItemModel" type="Object" optional="true">Object describing the data item in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <remarks>
+        ///    This function will first try to get the type name from the data item's value itself if it is a JSON light object; otherwise
+        ///    it will try to get it from the odata.type annotation applied to the data item in the container. Then, it will fallback to the data item model.
+        ///    If all attempts fail, it will return null.
+        /// </remarks>
+        /// <returns type="String">Data item type name; null if the type name cannot be found.</returns>
+
+        return (isComplex(value) && value[typeAnnotation]) ||
+            (container && container[name + "@" + typeAnnotation]) ||
+            (dataItemModel && dataItemModel.type) ||
+            (lookupNavigationPropertyType(dataItemModel, model)) ||
+            null;
+    };
+
+    var jsonLightDataItemModel = function (name, containerModel) {
+        /// <summary>Gets an object describing a data item in an OData conceptual schema.</summary>
+        /// <param name="name" type="String">Name of the data item for which the model is going to be retrieved.</param>
+        /// <param name="containerModel" type="Object">Object describing the owner of the data item in an OData conceptual schema.</param>
+        /// <returns type="Object">Object describing the data item; null if it cannot be found.</returns>
+
+        if (containerModel) {
+            return lookupProperty(containerModel.property, name) ||
+                lookupProperty(containerModel.navigationProperty, name);
+        }
+        return null;
+    };
+
+    var jsonLightIsEntry = function (data) {
+        /// <summary>Determines whether data represents a JSON light entry object.</summary>
+        /// <param name="data" type="Object">JSON light object to test.</param>
+        /// <returns type="Boolean">True if the data is JSON light entry object; false otherwise.</returns>
+
+        return isComplex(data) && ((odataAnnotationPrefix + "id") in data);
+    };
+
+    var jsonLightIsNavigationProperty = function (name, data, dataItemModel) {
+        /// <summary>Determines whether a data item in a JSON light object is a navigation property.</summary>
+        /// <param name="name" type="String">Name of the data item to test.</param>
+        /// <param name="data" type="Object">JSON light object that owns the data item.</param>
+        /// <param name="dataItemModel" type="Object">Object describing the data item in an OData conceptual schema.</param>
+        /// <returns type="Boolean">True if the data item is a navigation property; false otherwise.</returns>
+
+        djsassert(isComplex(data), "jsonLightIsNavProp - data is not an object!!");
+        if (!!data[name + "@" + navUrlAnnotation] || (dataItemModel && dataItemModel.relationship)) {
+            return true;
+        }
+
+        // Sniff the property value.
+        var value = isArray(data[name]) ? data[name][0] : data[name];
+        return jsonLightIsEntry(value);
+    };
+
+    var jsonLightIsPrimitiveType = function (typeName) {
+        /// <summary>Determines whether a type name is a primitive type in a JSON light payload.</summary>
+        /// <param name="typeName" type="String">Type name to test.</param>
+        /// <returns type="Boolean">True if the type name an EDM primitive type or an OData spatial type; false otherwise.</returns>
+
+        return isPrimitiveEdmType(typeName) || isGeographyEdmType(typeName) || isGeometryEdmType(typeName);
+    };
+
+    var jsonLightReadDataAnnotations = function (data, obj, baseURI, dataModel, model) {
+        /// <summary>Converts annotations found in a JSON light payload object to either properties or metadata.</summary>
+        /// <param name="data" type="Object">JSON light payload object containing the annotations to convert.</param>
+        /// <param name="obj" type="Object">Object that will store the converted annotations.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="dataModel" type="Object">Object describing the JSON light payload in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns>JSON light payload object with its annotations converted to either properties or metadata.</param>
+
+        for (var name in data) {
+            if (name.indexOf(".") > 0 && name.charAt(0) !== "#") {
+                var annotationInfo = jsonLightAnnotationInfo(name);
+                if (annotationInfo) {
+                    var annotationName = annotationInfo.name;
+                    var target = annotationInfo.target;
+                    var targetModel = null;
+                    var targetType = null;
+
+                    if (target) {
+                        targetModel = jsonLightDataItemModel(target, dataModel);
+                        targetType = jsonLightDataItemType(target, data[target], data, targetModel, model);
+                    }
+
+                    if (annotationInfo.isOData) {
+                        jsonLightApplyPayloadODataAnnotation(annotationName, target, targetType, data[name], data, obj, baseURI);
+                    } else {
+                        obj[name] = data[name];
+                    }
+                }
+            }
+        }
+        return obj;
+    };
+
+    var jsonLightApplyPayloadODataAnnotation = function (name, target, targetType, value, data, obj, baseURI) {
+        /// <summary>
+        ///   Processes a JSON Light payload OData annotation producing either a property, payload metadata, or property metadata on its owner object.
+        /// </summary>
+        /// <param name="name" type="String">Annotation name.</param>
+        /// <param name="target" type="String">Name of the property that is being targeted by the annotation.</param>
+        /// <param name="targetType" type="String">Type name of the target property.</param>
+        /// <param name="data" type="Object">JSON light object containing the annotation.</param>
+        /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+
+        var annotation = name.substring(odataAnnotationPrefix.length);
+
+        switch (annotation) {
+            case "navigationLinkUrl":
+                jsonLightApplyNavigationUrlAnnotation(annotation, target, targetType, value, data, obj, baseURI);
+                return;
+            case "nextLink":
+            case "count":
+                jsonLightApplyFeedAnnotation(annotation, target, value, obj, baseURI);
+                return;
+            case "mediaReadLink":
+            case "mediaEditLink":
+            case "mediaContentType":
+            case "mediaETag":
+                jsonLightApplyMediaAnnotation(annotation, target, targetType, value, obj, baseURI);
+                return;
+            default:
+                jsonLightApplyMetadataAnnotation(annotation, target, value, obj, baseURI);
+                return;
+        }
+    };
+
+    var jsonLightApplyMetadataAnnotation = function (name, target, value, obj, baseURI) {
+        /// <summary>
+        ///    Converts a JSON light annotation that applies to entry metadata only (i.e. odata.editLink or odata.readLink) and its value
+        ///    into their library's internal representation and saves it back to data.
+        /// </summary>
+        /// <param name="name" type="String">Annotation name.</param>
+        /// <param name="target" type="String">Name of the property on which the annotation should be applied.</param>
+        /// <param name="value" type="Object">Annotation value.</param>
+        /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+
+        var metadata = obj.__metadata = obj.__metadata || {};
+        var mappedName = jsonLightNameMap[name] || name;
+
+        if (name === "editLink") {
+            metadata.uri = normalizeURI(value, baseURI);
+            metadata[mappedName] = metadata.uri;
+            return;
+        }
+
+        if (name === "readLink" || name === "associationLinkUrl") {
+            value = normalizeURI(value, baseURI);
+        }
+
+        if (target) {
+            var propertiesMetadata = metadata.properties = metadata.properties || {};
+            var propertyMetadata = propertiesMetadata[target] = propertiesMetadata[target] || {};
+
+            if (name === "type") {
+                propertyMetadata[mappedName] = propertyMetadata[mappedName] || value;
+                return;
+            }
+            propertyMetadata[mappedName] = value;
+            return;
+        }
+        metadata[mappedName] = value;
+    };
+
+    var jsonLightApplyFeedAnnotation = function (name, target, value, obj, baseURI) {
+        /// <summary>
+        ///    Converts a JSON light annotation that applies to feeds only (i.e. odata.count or odata.nextlink) and its value
+        ///    into their library's internal representation and saves it back to data.
+        /// </summary>
+        /// <param name="name" type="String">Annotation name.</param>
+        /// <param name="target" type="String">Name of the property on which the annotation should be applied.</param>
+        /// <param name="value" type="Object">Annotation value.</param>
+        /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+
+        var mappedName = jsonLightNameMap[name];
+        var feed = target ? obj[target] : obj;
+        feed[mappedName] = (name === "nextLink") ? normalizeURI(value, baseURI) : value;
+    };
+
+    var jsonLightApplyMediaAnnotation = function (name, target, targetType, value, obj, baseURI) {
+        /// <summary>
+        ///    Converts a JSON light media annotation in and its value into their library's internal representation
+        ///    and saves it back to data or metadata.
+        /// </summary>
+        /// <param name="name" type="String">Annotation name.</param>
+        /// <param name="target" type="String">Name of the property on which the annotation should be applied.</param>
+        /// <param name="targetType" type="String">Type name of the target property.</param>
+        /// <param name="value" type="Object">Annotation value.</param>
+        /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+
+        var metadata = obj.__metadata = obj.__metadata || {};
+        var mappedName = jsonLightNameMap[name];
+
+        if (name === "mediaReadLink" || name === "mediaEditLink") {
+            value = normalizeURI(value, baseURI);
+        }
+
+        if (target) {
+            var propertiesMetadata = metadata.properties = metadata.properties || {};
+            var propertyMetadata = propertiesMetadata[target] = propertiesMetadata[target] || {};
+            propertyMetadata.type = propertyMetadata.type || targetType;
+
+            obj.__metadata = metadata;
+            obj[target] = obj[target] || { __mediaresource: {} };
+            obj[target].__mediaresource[mappedName] = value;
+            return;
+        }
+
+        metadata[mappedName] = value;
+    };
+
+    var jsonLightApplyNavigationUrlAnnotation = function (name, target, targetType, value, data, obj, baseURI) {
+        /// <summary>
+        ///    Converts a JSON light navigation property annotation and its value into their library's internal representation
+        ///    and saves it back to data o metadata.
+        /// </summary>
+        /// <param name="name" type="String">Annotation name.</param>
+        /// <param name="target" type="String">Name of the property on which the annotation should be applied.</param>
+        /// <param name="targetType" type="String">Type name of the target property.</param>
+        /// <param name="value" type="Object">Annotation value.</param>
+        /// <param name="data" type="Object">JSON light object containing the annotation.</param>
+        /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+
+        var metadata = obj.__metadata = obj.__metadata || {};
+        var propertiesMetadata = metadata.properties = metadata.properties || {};
+        var propertyMetadata = propertiesMetadata[target] = propertiesMetadata[target] || {};
+        var uri = normalizeURI(value, baseURI);
+
+        if (data.hasOwnProperty(target)) {
+            // The navigation property is inlined in the payload,
+            // so the navigation link url should be pushed to the object's
+            // property metadata instead.
+            propertyMetadata.navigationLinkUrl = uri;
+            return;
+        }
+        obj[target] = { __deferred: { uri: uri} };
+        propertyMetadata.type = propertyMetadata.type || targetType;
+    };
+
+
+    var jsonLightReadDataItemValue = function (value, typeName, dataItemMetadata, baseURI, dataItemModel, model, recognizeDates) {
+        /// <summary>Converts the value of a data item in a JSON light object to its library representation.</summary>
+        /// <param name="value">Data item value to convert.</param>
+        /// <param name="typeName" type="String">Type name of the data item.</param>
+        /// <param name="dataItemMetadata" type="Object">Object containing metadata about the data item.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="dataItemModel" type="Object" optional="true">Object describing the data item in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns>Data item value in its library representation.</param>
+
+        if (typeof value === "string") {
+            return jsonLightReadStringPropertyValue(value, typeName, recognizeDates);
+        }
+
+        if (!jsonLightIsPrimitiveType(typeName)) {
+            if (isArray(value)) {
+                return jsonLightReadCollectionPropertyValue(value, typeName, dataItemMetadata, baseURI, model, recognizeDates);
+            }
+
+            if (isComplex(value)) {
+                return jsonLightReadComplexPropertyValue(value, typeName, dataItemMetadata, baseURI, model, recognizeDates);
+            }
+        }
+        return value;
+    };
+
+    var jsonLightReadStringPropertyValue = function (value, propertyType, recognizeDates) {
+        /// <summary>Convertes the value of a string property in a JSON light object to its library representation.</summary>
+        /// <param name="value" type="String">String value to convert.</param>
+        /// <param name="propertyType" type="String">Type name of the property.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns>String property value in its library representation.</returns>
+
+        switch (propertyType) {
+            case EDM_TIME:
+                return parseDuration(value);
+            case EDM_DATETIME:
+                return parseDateTime(value, /*nullOnError*/false);
+            case EDM_DATETIMEOFFSET:
+                return parseDateTimeOffset(value, /*nullOnError*/false);
+        }
+
+        if (recognizeDates) {
+            return parseDateTime(value, /*nullOnError*/true) ||
+                   parseDateTimeOffset(value, /*nullOnError*/true) ||
+                   value;
+        }
+        return value;
+    };
+
+    var jsonLightReadCollectionPropertyValue = function (value, propertyType, propertyMetadata, baseURI, model, recognizeDates) {
+        /// <summary>Converts the value of a collection property in a JSON light object into its library representation.</summary>
+        /// <param name="value" type="Array">Collection property value to convert.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <param name="propertyMetadata" type="Object">Object containing metadata about the collection property.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Collection property value in its library representation.</returns>
+
+        var collectionType = getCollectionType(propertyType);
+        var itemsMetadata = [];
+        var items = [];
+
+        var i, len;
+        for (i = 0, len = value.length; i < len; i++) {
+            var itemType = jsonLightDataItemType(null, value[i]) || collectionType;
+            var itemMetadata = { type: itemType };
+            var item = jsonLightReadDataItemValue(value[i], itemType, itemMetadata, baseURI, null, model, recognizeDates);
+
+            if (!jsonLightIsPrimitiveType(itemType) && !isPrimitive(value[i])) {
+                itemsMetadata.push(itemMetadata);
+            }
+            items.push(item);
+        }
+
+        if (itemsMetadata.length > 0) {
+            propertyMetadata.elements = itemsMetadata;
+        }
+
+        return { __metadata: { type: propertyType }, results: items };
+    };
+
+    var jsonLightReadComplexPropertyValue = function (value, propertyType, propertyMetadata, baseURI, model, recognizeDates) {
+        /// <summary>Converts the value of a comples property in a JSON light object into its library representation.</summary>
+        /// <param name="value" type="Object">Complex property value to convert.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <param name="propertyMetadata" type="Object">Object containing metadata about the complx type property.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Complex property value in its library representation.</returns>
+
+        var complexValue = jsonLightReadObject(value, { type: propertyType }, baseURI, model, recognizeDates);
+        var complexMetadata = complexValue.__metadata;
+        var complexPropertiesMetadata = complexMetadata.properties;
+
+        if (complexPropertiesMetadata) {
+            propertyMetadata.properties = complexPropertiesMetadata;
+            delete complexMetadata.properties;
+        }
+        return complexValue;
+    };
+
+    var jsonLightReadNavigationPropertyValue = function (value, propertyInfo, baseURI, model, recognizeDates) {
+        /// <summary>Converts the value of a navigation property in a JSON light object into its library representation.</summary>
+        /// <param name="value">Navigation property property value to convert.</param>
+        /// <param name="propertyInfo" type="String">Information about the property whether it's an entry, feed or complex type.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Collection property value in its library representation.</returns>
+
+        if (isArray(value)) {
+            return jsonLightReadFeed(value, propertyInfo, baseURI, model, recognizeDates);
+        }
+
+        if (isComplex(value)) {
+            return jsonLightReadObject(value, propertyInfo, baseURI, model, recognizeDates);
+        }
+        return null;
+    };
+
+    var jsonLightReadObject = function (data, objectInfo, baseURI, model, recognizeDates) {
+        /// <summary>Converts a JSON light entry or complex type object into its library representation.</summary>
+        /// <param name="data" type="Object">JSON light entry or complex type object to convert.</param>
+        /// <param name="objectInfo" type="Object">Information about the entry or complex.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Entry or complex type object.</param>
+
+        objectInfo = objectInfo || {};
+        var actualType = data[typeAnnotation] || objectInfo.type || null;
+        var dataModel = lookupEntityType(actualType, model);
+        var isEntry = true;
+        if (!dataModel) {
+            isEntry = false;
+            dataModel = lookupComplexType(actualType, model);
+        }
+
+        var metadata = { type: actualType };
+        var obj = { __metadata: metadata };
+        var propertiesMetadata = {};
+        var baseTypeModel;
+        if (isEntry && dataModel && objectInfo.entitySet && objectInfo.contentTypeOdata == "minimalmetadata") {
+            var serviceURI = baseURI.substring(0, baseURI.lastIndexOf("$metadata"));
+            baseTypeModel = null; // check if the key model is in a parent type.
+            if (!dataModel.key) {
+                baseTypeModel = dataModel;
+            }
+            while (!!baseTypeModel && !baseTypeModel.key && baseTypeModel.baseType) {
+                baseTypeModel = lookupEntityType(baseTypeModel.baseType, model);
+            }
+
+            if (dataModel.key || (!!baseTypeModel && baseTypeModel.key)) {
+                var entryKey;
+                if (dataModel.key) {
+                    entryKey = jsonLightGetEntryKey(data, dataModel);
+                } else {
+                    entryKey = jsonLightGetEntryKey(data, baseTypeModel);
+                }
+                if (entryKey) {
+                    var entryInfo = {
+                        key: entryKey,
+                        entitySet: objectInfo.entitySet,
+                        functionImport: objectInfo.functionImport,
+                        containerName: objectInfo.containerName
+                    };
+                    jsonLightComputeUrisIfMissing(data, entryInfo, actualType, serviceURI, dataModel, baseTypeModel);
+                }
+            }
+        }
+
+        for (var name in data) {
+            if (name.indexOf("#") === 0) {
+                // This is an advertised function or action.
+                jsonLightReadAdvertisedFunctionOrAction(name.substring(1), data[name], obj, baseURI, model);
+            } else {
+                // Is name NOT an annotation?
+                if (name.indexOf(".") === -1) {
+                    if (!metadata.properties) {
+                        metadata.properties = propertiesMetadata;
+                    }
+
+                    var propertyValue = data[name];
+                    var propertyModel = propertyModel = jsonLightDataItemModel(name, dataModel);
+                    baseTypeModel = dataModel;
+                    while (!!dataModel && propertyModel === null && baseTypeModel.baseType) {
+                        baseTypeModel = lookupEntityType(baseTypeModel.baseType, model);
+                        propertyModel = propertyModel = jsonLightDataItemModel(name, baseTypeModel);
+                    }
+                    var isNavigationProperty = jsonLightIsNavigationProperty(name, data, propertyModel);
+                    var propertyType = jsonLightDataItemType(name, propertyValue, data, propertyModel, model);
+                    var propertyMetadata = propertiesMetadata[name] = propertiesMetadata[name] || { type: propertyType };
+                    if (isNavigationProperty) {
+                        var propertyInfo = {};
+                        if (objectInfo.entitySet !== undefined) {
+                            var navigationPropertyEntitySetName = lookupNavigationPropertyEntitySet(propertyModel, objectInfo.entitySet.name, model);
+                            propertyInfo = getEntitySetInfo(navigationPropertyEntitySetName, model);
+                        }
+                        propertyInfo.contentTypeOdata = objectInfo.contentTypeOdata;
+                        propertyInfo.kind = objectInfo.kind;
+                        propertyInfo.type = propertyType;
+                        obj[name] = jsonLightReadNavigationPropertyValue(propertyValue, propertyInfo, baseURI, model, recognizeDates);
+                    } else {
+                        obj[name] = jsonLightReadDataItemValue(propertyValue, propertyType, propertyMetadata, baseURI, propertyModel, model, recognizeDates);
+                    }
+                }
+            }
+        }
+
+        return jsonLightReadDataAnnotations(data, obj, baseURI, dataModel, model);
+    };
+
+    var jsonLightReadAdvertisedFunctionOrAction = function (name, value, obj, baseURI, model) {
+        /// <summary>Converts a JSON light advertised action or function object into its library representation.</summary>
+        /// <param name="name" type="String">Advertised action or function name.</param>
+        /// <param name="value">Advertised action or function value.</param>
+        /// <param name="obj" type="Object">Object that will the converted value of the advertised action or function.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing the action's or function's relative URIs.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <remarks>
+        ///     Actions and functions have the same representation in json light, so to disambiguate them the function uses
+        ///     the model object.  If available, the function will look for the functionImport object that describes the
+        ///     the action or the function.  If for whatever reason the functionImport can't be retrieved from the model (like
+        ///     there is no model available or there is no functionImport within the model), then the value is going to be treated
+        ///     as an advertised action and stored under obj.__metadata.actions.
+        /// </remarks>
+
+        if (!name || !isArray(value) && !isComplex(value)) {
+            return;
+        }
+
+        var isFunction = false;
+        var nsEnd = name.lastIndexOf(".");
+        var simpleName = name.substring(nsEnd + 1);
+        var containerName = (nsEnd > -1) ? name.substring(0, nsEnd) : "";
+
+        var container = (simpleName === name || containerName.indexOf(".") === -1) ?
+            lookupDefaultEntityContainer(model) :
+            lookupEntityContainer(containerName, model);
+
+        if (container) {
+            var functionImport = lookupFunctionImport(container.functionImport, simpleName);
+            if (functionImport && !!functionImport.isSideEffecting) {
+                isFunction = !parseBool(functionImport.isSideEffecting);
+            }
+        }
+
+        var metadata = obj.__metadata;
+        var targetName = isFunction ? "functions" : "actions";
+        var metadataURI = normalizeURI(name, baseURI);
+        var items = (isArray(value)) ? value : [value];
+
+        var i, len;
+        for (i = 0, len = items.length; i < len; i++) {
+            var item = items[i];
+            if (item) {
+                var targetCollection = metadata[targetName] = metadata[targetName] || [];
+                var actionOrFunction = { metadata: metadataURI, title: item.title, target: normalizeURI(item.target, baseURI) };
+                targetCollection.push(actionOrFunction);
+            }
+        }
+    };
+
+    var jsonLightReadFeed = function (data, feedInfo, baseURI, model, recognizeDates) {
+        /// <summary>Converts a JSON light feed or top level collection property object into its library representation.</summary>
+        /// <param name="data" type="Object">JSON light feed object to convert.</param>
+        /// <param name="typeName" type="String">Type name of the feed or collection items.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Feed or top level collection object.</param>
+
+        var items = isArray(data) ? data : data.value;
+        var entries = [];
+        var i, len, entry;
+        for (i = 0, len = items.length; i < len; i++) {
+            entry = jsonLightReadObject(items[i], feedInfo, baseURI, model, recognizeDates);
+            entries.push(entry);
+        }
+
+        var feed = { results: entries };
+
+        if (isComplex(data)) {
+            for (var name in data) {
+                if (name.indexOf("#") === 0) {
+                    // This is an advertised function or action.
+                    feed.__metadata = feed.__metadata || {};
+                    jsonLightReadAdvertisedFunctionOrAction(name.substring(1), data[name], feed, baseURI, model);
+                }
+            }
+            feed = jsonLightReadDataAnnotations(data, feed, baseURI);
+        }
+        return feed;
+    };
+
+    var jsonLightGetEntryKey = function (data, entityModel) {
+        /// <summary>Gets the key of an entry.</summary>
+        /// <param name="data" type="Object">JSON light entry.</param>
+        /// <param name="entityModel" type="String">Object describing the entry Model</param>
+        /// <returns type="string">Entry instance key.</returns>
+
+        var entityInstanceKey;
+        var entityKeys = entityModel.key.propertyRef;
+        var type;
+        entityInstanceKey = "(";
+        if (entityKeys.length == 1) {
+            type = lookupProperty(entityModel.property, entityKeys[0].name).type;
+            entityInstanceKey += formatLiteral(data[entityKeys[0].name], type);
+        } else {
+            var first = true;
+            for (var i = 0; i < entityKeys.length; i++) {
+                if (!first) {
+                    entityInstanceKey += ",";
+                } else {
+                    first = false;
+                }
+                type = lookupProperty(entityModel.property, entityKeys[i].name).type;
+                entityInstanceKey += entityKeys[i].name + "=" + formatLiteral(data[entityKeys[i].name], type);
+            }
+        }
+        entityInstanceKey += ")";
+        return entityInstanceKey;
+    };
+
+
+    var jsonLightComputeUrisIfMissing = function (data, entryInfo, actualType, serviceURI, entityModel, baseTypeModel) {
+        /// <summary>Compute the URI according to OData conventions if it doesn't exist</summary>
+        /// <param name="data" type="Object">JSON light entry.</param>
+        /// <param name="entryInfo" type="Object">Information about the entry includes type, key, entitySet and entityContainerName.</param>
+        /// <param name="actualType" type="String">Type of the entry</param>
+        /// <param name="serviceURI" type="String">Base URI the service.</param>
+        /// <param name="entityModel" type="Object">Object describing an OData conceptual schema of the entry.</param>
+        /// <param name="baseTypeModel" type="Object" optional="true">Object escribing an OData conceptual schema of the baseType if it exists.</param>
+
+        var lastIdSegment = data[jsonLightAnnotations.id] || data[jsonLightAnnotations.read] || data[jsonLightAnnotations.edit] || entryInfo.entitySet.name + entryInfo.key;
+        data[jsonLightAnnotations.id] = serviceURI + lastIdSegment;
+        if (!data[jsonLightAnnotations.edit]) {
+            data[jsonLightAnnotations.edit] = entryInfo.entitySet.name + entryInfo.key;
+            if (entryInfo.entitySet.entityType != actualType) {
+                data[jsonLightAnnotations.edit] += "/" + actualType;
+            }
+        }
+        data[jsonLightAnnotations.read] = data[jsonLightAnnotations.read] || data[jsonLightAnnotations.edit];
+        if (!data[jsonLightAnnotations.etag]) {
+            var etag = jsonLightComputeETag(data, entityModel, baseTypeModel);
+            if (!!etag) {
+                data[jsonLightAnnotations.etag] = etag;
+            }
+        }
+
+        jsonLightComputeStreamLinks(data, entityModel, baseTypeModel);
+        jsonLightComputeNavigationAndAssociationProperties(data, entityModel, baseTypeModel);
+        jsonLightComputeFunctionImports(data, entryInfo);
+    };
+
+    var jsonLightComputeETag = function (data, entityModel, baseTypeModel) {
+        /// <summary>Computes the etag of an entry</summary>
+        /// <param name="data" type="Object">JSON light entry.</param>
+        /// <param name="entryInfo" type="Object">Object describing the entry model.</param>
+        /// <param name="baseTypeModel" type="Object"  optional="true">Object describing an OData conceptual schema of the baseType if it exists.</param>
+        /// <returns type="string">Etag value</returns>
+        var etag = "";
+        var propertyModel;
+        for (var i = 0; entityModel.property && i < entityModel.property.length; i++) {
+            propertyModel = entityModel.property[i];
+            etag = jsonLightAppendValueToEtag(data, etag, propertyModel);
+
+        }
+        if (baseTypeModel) {
+            for (i = 0; baseTypeModel.property && i < baseTypeModel.property.length; i++) {
+                propertyModel = baseTypeModel.property[i];
+                etag = jsonLightAppendValueToEtag(data, etag, propertyModel);
+            }
+        }
+        if (etag.length > 0) {
+            return etag + "\"";
+        }
+        return null;
+    };
+
+    var jsonLightAppendValueToEtag = function (data, etag, propertyModel) {
+        /// <summary>Adds a propery value to the etag after formatting.</summary>
+        /// <param name="data" type="Object">JSON light entry.</param>
+        /// <param name="etag" type="Object">value of the etag.</param>
+        /// <param name="propertyModel" type="Object">Object describing an OData conceptual schema of the property.</param>
+        /// <returns type="string">Etag value</returns>
+
+        if (propertyModel.concurrencyMode == "Fixed") {
+            if (etag.length > 0) {
+                etag += ",";
+            } else {
+                etag += "W/\"";
+            }
+            if (data[propertyModel.name] !== null) {
+                etag += formatLiteral(data[propertyModel.name], propertyModel.type);
+            } else {
+                etag += "null";
+            }
+        }
+        return etag;
+    };
+
+    var jsonLightComputeNavigationAndAssociationProperties = function (data, entityModel, baseTypeModel) {
+        /// <summary>Adds navigation links to the entry metadata</summary>
+        /// <param name="data" type="Object">JSON light entry.</param>
+        /// <param name="entityModel" type="Object">Object describing the entry model.</param>
+        /// <param name="baseTypeModel" type="Object"  optional="true">Object describing an OData conceptual schema of the baseType if it exists.</param>
+
+        var navigationLinkAnnotation = "@odata.navigationLinkUrl";
+        var associationLinkAnnotation = "@odata.associationLinkUrl";
+        var navigationPropertyName, navigationPropertyAnnotation, associationPropertyAnnotation;
+        for (var i = 0; entityModel.navigationProperty && i < entityModel.navigationProperty.length; i++) {
+            navigationPropertyName = entityModel.navigationProperty[i].name;
+            navigationPropertyAnnotation = navigationPropertyName + navigationLinkAnnotation;
+            if (data[navigationPropertyAnnotation] === undefined) {
+                data[navigationPropertyAnnotation] = data[jsonLightAnnotations.edit] + "/" + encodeURIComponent(navigationPropertyName);
+            }
+            associationPropertyAnnotation = navigationPropertyName + associationLinkAnnotation;
+            if (data[associationPropertyAnnotation] === undefined) {
+                data[associationPropertyAnnotation] = data[jsonLightAnnotations.edit] + "/$links/" + encodeURIComponent(navigationPropertyName);
+            }
+        }
+
+        if (baseTypeModel && baseTypeModel.navigationProperty) {
+            for (i = 0; i < baseTypeModel.navigationProperty.length; i++) {
+                navigationPropertyName = baseTypeModel.navigationProperty[i].name;
+                navigationPropertyAnnotation = navigationPropertyName + navigationLinkAnnotation;
+                if (data[navigationPropertyAnnotation] === undefined) {
+                    data[navigationPropertyAnnotation] = data[jsonLightAnnotations.edit] + "/" + encodeURIComponent(navigationPropertyName);
+                }
+                associationPropertyAnnotation = navigationPropertyName + associationLinkAnnotation;
+                if (data[associationPropertyAnnotation] === undefined) {
+                    data[associationPropertyAnnotation] = data[jsonLightAnnotations.edit] + "/$links/" + encodeURIComponent(navigationPropertyName);
+                }
+            }
+        }
+    };
+
+    var formatLiteral = function (value, type) {
+        /// <summary>Formats a value according to Uri literal format</summary>
+        /// <param name="value">Value to be formatted.</param>
+        /// <param name="type">Edm type of the value</param>
+        /// <returns type="string">Value after formatting</returns>
+
+        value = "" + formatRowLiteral(value, type);
+        value = encodeURIComponent(value.replace("'", "''"));
+        switch ((type)) {
+            case "Edm.Binary":
+                return "X'" + value + "'";
+            case "Edm.DateTime":
+                return "datetime" + "'" + value + "'";
+            case "Edm.DateTimeOffset":
+                return "datetimeoffset" + "'" + value + "'";
+            case "Edm.Decimal":
+                return value + "M";
+            case "Edm.Guid":
+                return "guid" + "'" + value + "'";
+            case "Edm.Int64":
+                return value + "L";
+            case "Edm.Float":
+                return value + "f";
+            case "Edm.Double":
+                return value + "D";
+            case "Edm.Geography":
+                return "geography" + "'" + value + "'";
+            case "Edm.Geometry":
+                return "geometry" + "'" + value + "'";
+            case "Edm.Time":
+                return "time" + "'" + value + "'";
+            case "Edm.String":
+                return "'" + value + "'";
+            default:
+                return value;
+        }
+    };
+
+
+    var formatRowLiteral = function (value, type) {
+        switch (type) {
+            case "Edm.Binary":
+                return convertByteArrayToHexString(value);
+            default:
+                return value;
+        }
+    };
+
+    var jsonLightComputeFunctionImports = function (data, entryInfo) {
+        /// <summary>Adds functions and actions links to the entry metadata</summary>
+        /// <param name="entry" type="Object">JSON light entry.</param>
+        /// <param name="entityInfo" type="Object">Object describing the entry</param>
+
+        var functionImport = entryInfo.functionImport || [];
+        for (var i = 0; i < functionImport.length; i++) {
+            if (functionImport[i].isBindable && functionImport[i].parameter[0] && functionImport[i].parameter[0].type == entryInfo.entitySet.entityType) {
+                var functionImportAnnotation = "#" + entryInfo.containerName + "." + functionImport[i].name;
+                if (data[functionImportAnnotation] == undefined) {
+                    data[functionImportAnnotation] = {
+                        title: functionImport[i].name,
+                        target: data[jsonLightAnnotations.edit] + "/" + functionImport[i].name
+                    };
+                }
+            }
+        }
+    };
+
+    var jsonLightComputeStreamLinks = function (data, entityModel, baseTypeModel) {
+        /// <summary>Adds stream links to the entry metadata</summary>
+        /// <param name="data" type="Object">JSON light entry.</param>
+        /// <param name="entityModel" type="Object">Object describing the entry model.</param>
+        /// <param name="baseTypeModel" type="Object"  optional="true">Object describing an OData conceptual schema of the baseType if it exists.</param>
+
+        if (entityModel.hasStream || (baseTypeModel && baseTypeModel.hasStream)) {
+            data[jsonLightAnnotations.mediaEdit] = data[jsonLightAnnotations.mediaEdit] || data[jsonLightAnnotations.mediaEdit] + "/$value";
+            data[jsonLightAnnotations.mediaRead] = data[jsonLightAnnotations.mediaRead] || data[jsonLightAnnotations.mediaEdit];
+        }
+    };
+
+    var jsonLightReadTopPrimitiveProperty = function (data, typeName, baseURI, recognizeDates) {
+        /// <summary>Converts a JSON light top level primitive property object into its library representation.</summary>
+        /// <param name="data" type="Object">JSON light feed object to convert.</param>
+        /// <param name="typeName" type="String">Type name of the primitive property.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Top level primitive property object.</param>
+
+        var metadata = { type: typeName };
+        var value = jsonLightReadDataItemValue(data.value, typeName, metadata, baseURI, null, null, recognizeDates);
+        return jsonLightReadDataAnnotations(data, { __metadata: metadata, value: value }, baseURI);
+    };
+
+    var jsonLightReadTopCollectionProperty = function (data, typeName, baseURI, model, recognizeDates) {
+        /// <summary>Converts a JSON light top level collection property object into its library representation.</summary>
+        /// <param name="data" type="Object">JSON light feed object to convert.</param>
+        /// <param name="typeName" type="String">Type name of the collection property.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Top level collection property object.</param>
+
+        var propertyMetadata = {};
+        var value = jsonLightReadCollectionPropertyValue(data.value, typeName, propertyMetadata, baseURI, model, recognizeDates);
+        extend(value.__metadata, propertyMetadata);
+        return jsonLightReadDataAnnotations(data, value, baseURI);
+    };
+
+    var jsonLightReadLinksDocument = function (data, baseURI) {
+        /// <summary>Converts a JSON light links collection object to its library representation.</summary>
+        /// <param name="data" type="Object">JSON light link object to convert.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <returns type="Object">Links collection object.</param>
+
+        var items = data.value;
+        if (!isArray(items)) {
+            return jsonLightReadLink(data, baseURI);
+        }
+
+        var results = [];
+        var i, len;
+        for (i = 0, len = items.length; i < len; i++) {
+            results.push(jsonLightReadLink(items[i], baseURI));
+        }
+
+        var links = { results: results };
+        return jsonLightReadDataAnnotations(data, links, baseURI);
+    };
+
+    var jsonLightReadLink = function (data, baseURI) {
+        /// <summary>Converts a JSON light link object to its library representation.</summary>
+        /// <param name="data" type="Object">JSON light link object to convert.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <returns type="Object">Link object.</param>
+
+        var link = { uri: normalizeURI(data.url, baseURI) };
+
+        link = jsonLightReadDataAnnotations(data, link, baseURI);
+        var metadata = link.__metadata || {};
+        var metadataProperties = metadata.properties || {};
+
+        jsonLightRemoveTypePropertyMetadata(metadataProperties.url);
+        renameProperty(metadataProperties, "url", "uri");
+
+        return link;
+    };
+
+    var jsonLightRemoveTypePropertyMetadata = function (propertyMetadata) {
+        /// <summary>Removes the type property from a property metadata object.</summary>
+        /// <param name="propertyMetadata" type="Object">Property metadata object.</param>
+
+        if (propertyMetadata) {
+            delete propertyMetadata.type;
+        }
+    };
+
+    var jsonLightReadSvcDocument = function (data, baseURI) {
+        /// <summary>Converts a JSON light service document object to its library representation.</summary>
+        /// <param name="data" type="Object">JSON light service document object to convert.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <returns type="Object">Link object.</param>
+
+        var items = data.value;
+        var collections = [];
+        var workspace = jsonLightReadDataAnnotations(data, { collections: collections }, baseURI);
+
+        var metadata = workspace.__metadata || {};
+        var metadataProperties = metadata.properties || {};
+
+        jsonLightRemoveTypePropertyMetadata(metadataProperties.value);
+        renameProperty(metadataProperties, "value", "collections");
+
+        var i, len;
+        for (i = 0, len = items.length; i < len; i++) {
+            var item = items[i];
+            var collection = { title: item.name, href: normalizeURI(item.url, baseURI) };
+
+            collection = jsonLightReadDataAnnotations(item, collection, baseURI);
+            metadata = collection.__metadata || {};
+            metadataProperties = metadata.properties || {};
+
+            jsonLightRemoveTypePropertyMetadata(metadataProperties.name);
+            jsonLightRemoveTypePropertyMetadata(metadataProperties.url);
+
+            renameProperty(metadataProperties, "name", "title");
+            renameProperty(metadataProperties, "url", "href");
+
+            collections.push(collection);
+        }
+
+        return { workspaces: [workspace] };
+    };
+
+    var jsonLightMakePayloadInfo = function (kind, type) {
+        /// <summary>Creates an object containing information for the json light payload.</summary>
+        /// <param name="kind" type="String">JSON light payload kind, one of the PAYLOADTYPE_XXX constant values.</param>
+        /// <param name="typeName" type="String">Type name of the JSON light payload.</param>
+        /// <returns type="Object">Object with kind and type fields.</returns>
+
+        /// <field name="kind" type="String">Kind of the JSON light payload. One of the PAYLOADTYPE_XXX constant values.</field>
+        /// <field name="type" type="String">Data type of the JSON light payload.</field>
+
+        return { kind: kind, type: type || null };
+    };
+
+    var jsonLightPayloadInfo = function (data, model, inferFeedAsComplexType) {
+        /// <summary>Infers the information describing the JSON light payload from its metadata annotation, structure, and data model.</summary>
+        /// <param name="data" type="Object">Json light response payload object.</param>
+        /// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
+        /// <param name="inferFeedAsComplexType" type="Boolean">True if a JSON light payload that looks like a feed should be treated as a complex type property instead.</param>
+        /// <remarks>
+        ///     If the arguments passed to the function don't convey enough information about the payload to determine without doubt that the payload is a feed then it
+        ///     will try to use the payload object structure instead.  If the payload looks like a feed (has value property that is an array or non-primitive values) then
+        ///     the function will report its kind as PAYLOADTYPE_FEED unless the inferFeedAsComplexType flag is set to true. This flag comes from the user request
+        ///     and allows the user to control how the library behaves with an ambigous JSON light payload.
+        /// </remarks>
+        /// <returns type="Object">
+        ///     Object with kind and type fields. Null if there is no metadata annotation or the payload info cannot be obtained..
+        /// </returns>
+
+        var metadataUri = data[metadataAnnotation];
+        if (!metadataUri || typeof metadataUri !== "string") {
+            return null;
+        }
+
+        var fragmentStart = metadataUri.lastIndexOf("#");
+        if (fragmentStart === -1) {
+            return jsonLightMakePayloadInfo(PAYLOADTYPE_SVCDOC);
+        }
+
+        var elementStart = metadataUri.indexOf("@Element", fragmentStart);
+        var fragmentEnd = elementStart - 1;
+
+        if (fragmentEnd < 0) {
+            fragmentEnd = metadataUri.indexOf("?", fragmentStart);
+            if (fragmentEnd === -1) {
+                fragmentEnd = metadataUri.length;
+            }
+        }
+
+        var fragment = metadataUri.substring(fragmentStart + 1, fragmentEnd);
+        if (fragment.indexOf("/$links/") > 0) {
+            return jsonLightMakePayloadInfo(PAYLOADTYPE_LINKS);
+        }
+
+        var fragmentParts = fragment.split("/");
+        if (fragmentParts.length >= 0) {
+            var qualifiedName = fragmentParts[0];
+            var typeCast = fragmentParts[1];
+
+            if (jsonLightIsPrimitiveType(qualifiedName)) {
+                return jsonLightMakePayloadInfo(PAYLOADTYPE_PRIMITIVE, qualifiedName);
+            }
+
+            if (isCollectionType(qualifiedName)) {
+                return jsonLightMakePayloadInfo(PAYLOADTYPE_COLLECTION, qualifiedName);
+            }
+
+            var entityType = typeCast;
+            var entitySet, functionImport, containerName;
+            if (!typeCast) {
+                var nsEnd = qualifiedName.lastIndexOf(".");
+                var simpleName = qualifiedName.substring(nsEnd + 1);
+                var container = (simpleName === qualifiedName) ?
+                    lookupDefaultEntityContainer(model) :
+                    lookupEntityContainer(qualifiedName.substring(0, nsEnd), model);
+
+                if (container) {
+                    entitySet = lookupEntitySet(container.entitySet, simpleName);
+                    functionImport = container.functionImport;
+                    containerName = container.name;
+                    entityType = !!entitySet ? entitySet.entityType : null;
+                }
+            }
+
+            var info;
+            if (elementStart > 0) {
+                info = jsonLightMakePayloadInfo(PAYLOADTYPE_OBJECT, entityType);
+                info.entitySet = entitySet;
+                info.functionImport = functionImport;
+                info.containerName = containerName;
+                return info;
+            }
+
+            if (entityType) {
+                info = jsonLightMakePayloadInfo(PAYLOADTYPE_FEED, entityType);
+                info.entitySet = entitySet;
+                info.functionImport = functionImport;
+                info.containerName = containerName;
+                return info;
+            }
+
+            if (isArray(data.value) && !lookupComplexType(qualifiedName, model)) {
+                var item = data.value[0];
+                if (!isPrimitive(item)) {
+                    if (jsonLightIsEntry(item) || !inferFeedAsComplexType) {
+                        return jsonLightMakePayloadInfo(PAYLOADTYPE_FEED, null);
+                    }
+                }
+            }
+
+            return jsonLightMakePayloadInfo(PAYLOADTYPE_OBJECT, qualifiedName);
+        }
+
+        return null;
+    };
+
+    var jsonLightReadPayload = function (data, model, recognizeDates, inferFeedAsComplexType, contentTypeOdata) {
+        /// <summary>Converts a JSON light response payload object into its library's internal representation.</summary>
+        /// <param name="data" type="Object">Json light response payload object.</param>
+        /// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <param name="inferFeedAsComplexType" type="Boolean">True if a JSON light payload that looks like a feed should be reported as a complex type property instead.</param>
+        /// <param name="contentTypeOdata" type="string">Includes the type of json ( minimalmetadata, fullmetadata .. etc )</param>
+        /// <returns type="Object">Object in the library's representation.</returns>
+
+        if (!isComplex(data)) {
+            return data;
+        }
+
+        contentTypeOdata = contentTypeOdata || "minimalmetadata";
+        var baseURI = data[metadataAnnotation];
+        var payloadInfo = jsonLightPayloadInfo(data, model, inferFeedAsComplexType);
+        if (assigned(payloadInfo)) {
+            payloadInfo.contentTypeOdata = contentTypeOdata;
+        }
+        var typeName = null;
+        if (payloadInfo) {
+            delete data[metadataAnnotation];
+
+            typeName = payloadInfo.type;
+            switch (payloadInfo.kind) {
+                case PAYLOADTYPE_FEED:
+                    return jsonLightReadFeed(data, payloadInfo, baseURI, model, recognizeDates);
+                case PAYLOADTYPE_COLLECTION:
+                    return jsonLightReadTopCollectionProperty(data, typeName, baseURI, model, recognizeDates);
+                case PAYLOADTYPE_PRIMITIVE:
+                    return jsonLightReadTopPrimitiveProperty(data, typeName, baseURI, recognizeDates);
+                case PAYLOADTYPE_SVCDOC:
+                    return jsonLightReadSvcDocument(data, baseURI);
+                case PAYLOADTYPE_LINKS:
+                    return jsonLightReadLinksDocument(data, baseURI);
+            }
+        }
+        return jsonLightReadObject(data, payloadInfo, baseURI, model, recognizeDates);
+    };
+
+    var jsonLightSerializableMetadata = ["type", "etag", "media_src", "edit_media", "content_type", "media_etag"];
+
+    var formatJsonLight = function (obj, context) {
+        /// <summary>Converts an object in the library's internal representation to its json light representation.</summary>
+        /// <param name="obj" type="Object">Object the library's internal representation.</param>
+        /// <param name="context" type="Object">Object with the serialization context.</param>
+        /// <returns type="Object">Object in its json light representation.</returns>
+
+        // Regular expression used to test that the uri is for a $links document.
+        var linksUriRE = /\/\$links\//;
+        var data = {};
+        var metadata = obj.__metadata;
+
+        var islinks = context && linksUriRE.test(context.request.requestUri);
+        formatJsonLightData(obj, (metadata && metadata.properties), data, islinks);
+        return data;
+    };
+
+    var formatJsonLightMetadata = function (metadata, data) {
+        /// <summary>Formats an object's metadata into the appropriate json light annotations and saves them to data.</summary>
+        /// <param name="obj" type="Object">Object whose metadata is going to be formatted as annotations.</param>
+        /// <param name="data" type="Object">Object on which the annotations are going to be stored.</param>
+
+        if (metadata) {
+            var i, len;
+            for (i = 0, len = jsonLightSerializableMetadata.length; i < len; i++) {
+                // There is only a subset of metadata values that are interesting during update requests.
+                var name = jsonLightSerializableMetadata[i];
+                var qName = odataAnnotationPrefix + (jsonLightNameMap[name] || name);
+                formatJsonLightAnnotation(qName, null, metadata[name], data);
+            }
+        }
+    };
+
+    var formatJsonLightData = function (obj, pMetadata, data, isLinks) {
+        /// <summary>Formats an object's data into the appropriate json light values and saves them to data.</summary>
+        /// <param name="obj" type="Object">Object whose data is going to be formatted.</param>
+        /// <param name="pMetadata" type="Object">Object that contains metadata for the properties that are being formatted.</param>
+        /// <param name="data" type="Object">Object on which the formatted values are going to be stored.</param>
+        /// <param name="isLinks" type="Boolean">True if a links document is being formatted.  False otherwise.</param>
+
+        for (var key in obj) {
+            var value = obj[key];
+            if (key === "__metadata") {
+                // key is the object metadata.
+                formatJsonLightMetadata(value, data);
+            } else if (key.indexOf(".") === -1) {
+                // key is an regular property or array element.
+                if (isLinks && key === "uri") {
+                    formatJsonLightEntityLink(value, data);
+                } else {
+                    formatJsonLightProperty(key, value, pMetadata, data, isLinks);
+                }
+            } else {
+                data[key] = value;
+            }
+        }
+    };
+
+    var formatJsonLightProperty = function (name, value, pMetadata, data) {
+        /// <summary>Formats an object's value identified by name to its json light representation and saves it to data.</summary>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="value">Property value.</param>
+        /// <param name="pMetadata" type="Object">Object that contains metadata for the property that is being formatted.</param>
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+
+        // Get property type from property metadata
+        var propertyMetadata = pMetadata && pMetadata[name] || { properties: undefined, type: undefined };
+        var typeName = dataItemTypeName(value, propertyMetadata);
+
+        if (isPrimitive(value) || !value) {
+            // It is a primitive value then.
+            formatJsonLightAnnotation(typeAnnotation, name, typeName, data);
+            data[name] = value;
+            return;
+        }
+
+        if (isFeed(value, typeName) || isEntry(value)) {
+            formatJsonLightInlineProperty(name, value, data);
+            return;
+        }
+
+        if (!typeName && isDeferred(value)) {
+            // It is really a deferred property.
+            formatJsonLightDeferredProperty(name, value, data);
+            return;
+        }
+
+        if (isCollection(value, typeName)) {
+            // The thing is a collection, format it as one.
+            if (getCollectionType(typeName)) {
+                formatJsonLightAnnotation(typeAnnotation, name, typeName, data);
+            }
+            formatJsonLightCollectionProperty(name, value, data);
+            return;
+        }
+
+        djsassert(isComplex(value), "formatJsonLightProperty - Value is not a complex type value");
+
+        // Format the complex property value in a new object in data[name].
+        data[name] = {};
+        formatJsonLightAnnotation(typeAnnotation, null, typeName, data[name]);
+        formatJsonLightData(value, propertyMetadata.properties, data[name]);
+    };
+
+    var formatJsonLightEntityLink = function (value, data) {
+        /// <summary>Formats an entity link in a $links document and saves it into data.</summary>
+        /// <param name="value" type="String">Entity link value.</summary>
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+        data.url = value;
+    };
+
+    var formatJsonLightDeferredProperty = function (name, value, data) {
+        /// <summary>Formats the object value's identified by name as an odata.navigalinkurl annotation and saves it to data.</summary>
+        /// <param name="name" type="String">Name of the deferred property to be formatted.</param>
+        /// <param name="value" type="Object">Deferred property value to be formatted.</param>
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+
+        formatJsonLightAnnotation(navUrlAnnotation, name, value.__deferred.uri, data);
+    };
+
+    var formatJsonLightCollectionProperty = function (name, value, data) {
+        /// <summary>Formats a collection property in obj identified by name as a json light collection property and saves it to data.</summary>
+        /// <param name="name" type="String">Name of the collection property to be formatted.</param>
+        /// <param name="value" type="Object">Collection property value to be formatted.</param>
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+
+        data[name] = [];
+        var items = isArray(value) ? value : value.results;
+        formatJsonLightData(items, null, data[name]);
+    };
+
+    var formatJsonLightInlineProperty = function (name, value, data) {
+        /// <summary>Formats an inline feed or entry property in obj identified by name as a json light value and saves it to data.</summary>
+        /// <param name="name" type="String">Name of the inline feed or entry property to be formatted.</param>
+        /// <param name="value" type="Object or Array">Value of the inline feed or entry property.</param>
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+
+        if (isFeed(value)) {
+            data[name] = [];
+            // Format each of the inline feed entries
+            var entries = isArray(value) ? value : value.results;
+            var i, len;
+            for (i = 0, len = entries.length; i < len; i++) {
+                formatJsonLightInlineEntry(name, entries[i], true, data);
+            }
+            return;
+        }
+        formatJsonLightInlineEntry(name, value, false, data);
+    };
+
+    var formatJsonLightInlineEntry = function (name, value, inFeed, data) {
+        /// <summary>Formats an inline entry value in the property identified by name as a json light value and saves it to data.</summary>
+        /// <param name="name" type="String">Name of the inline feed or entry property that owns the entry formatted.</param>
+        /// <param name="value" type="Object">Inline entry value to be formatted.</param>
+        /// <param name="inFeed" type="Boolean">True if the entry is in an inline feed; false otherwise.
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+
+        // This might be a bind instead of a deep insert.
+        var uri = value.__metadata && value.__metadata.uri;
+        if (uri) {
+            formatJsonLightBinding(name, uri, inFeed, data);
+            return;
+        }
+
+        var entry = formatJsonLight(value);
+        if (inFeed) {
+            data[name].push(entry);
+            return;
+        }
+        data[name] = entry;
+    };
+
+    var formatJsonLightBinding = function (name, uri, inFeed, data) {
+        /// <summary>Formats an entry binding in the inline property in obj identified by name as an odata.bind annotation and saves it to data.</summary>
+        /// <param name="name" type="String">Name of the inline property that has the binding to be formated.</param>
+        /// <param name="uri" type="String">Uri to the bound entry.</param>
+        /// <param name="inFeed" type="Boolean">True if the binding is in an inline feed; false otherwise.
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+
+        var bindingName = name + bindAnnotation;
+        if (inFeed) {
+            // The binding is inside an inline feed, so merge it with whatever other bindings already exist in data.
+            data[bindingName] = data[bindingName] || [];
+            data[bindingName].push(uri);
+            return;
+        }
+        // The binding is on an inline entry; it can be safely overwritten.
+        data[bindingName] = uri;
+    };
+
+    var formatJsonLightAnnotation = function (qName, target, value, data) {
+        /// <summary>Formats a value as a json light annotation and stores it in data</summary>
+        /// <param name="qName" type="String">Qualified name of the annotation.</param>
+        /// <param name="target" type="String">Name of the property that the metadata value targets.</param>
+        /// <param name="value">Annotation value.</param>
+        /// <param name="data" type="Object">Object on which the annotation is going to be stored.</param>
+
+        if (value !== undefined) {
+            if(target) {
+                data[target + "@" + qName] = value;
+            }
+            else {
+                data[qName] = value;
+            }
+        }
+    };
+
+    // DATAJS INTERNAL START
+    odata.jsonLightReadPayload = jsonLightReadPayload;
+    odata.formatJsonLight = formatJsonLight;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
+
+
+
diff --git a/JSLib/src/odata-json.js b/JSLib/src/odata-json.js
new file mode 100644
index 0000000..8f11857
--- /dev/null
+++ b/JSLib/src/odata-json.js
@@ -0,0 +1,379 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-json.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports 
+
+    var defined = datajs.defined;
+    var extend = datajs.extend;
+    var isArray = datajs.isArray;
+    var isDate = datajs.isDate;
+    var normalizeURI = datajs.normalizeURI;
+    var parseInt10 = datajs.parseInt10;
+
+    var contentType = odata.contentType;
+    var jsonLightReadPayload = odata.jsonLightReadPayload;
+    var formatDateTimeOffset = odata.formatDateTimeOffset;
+    var formatDuration = odata.formatDuration;
+    var formatJsonLight = odata.formatJsonLight;
+    var formatNumberWidth = odata.formatNumberWidth;
+    var getCanonicalTimezone = odata.getCanonicalTimezone;
+    var handler = odata.handler;
+    var isComplex = odata.isComplex;
+    var lookupComplexType = odata.lookupComplexType;
+    var lookupEntityType = odata.lookupEntityType;
+    var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
+    var maxVersion = odata.maxVersion;
+    var parseDateTime = odata.parseDateTime;
+    var parseDuration = odata.parseDuration;
+    var parseTimezone = odata.parseTimezone;
+    var payloadTypeOf = odata.payloadTypeOf;
+    var traverse = odata.traverse;
+
+    // CONTENT START
+
+    var jsonMediaType = "application/json";
+    var jsonContentType = contentType(jsonMediaType);
+
+    var jsonReadAdvertisedActionsOrFunctions = function (value) {
+        /// <summary>Reads and object containing action or function metadata and maps them into a single array of objects.</summary>
+        /// <param name="value" type="Object">Object containing action or function metadata.</param>
+        /// <returns type="Array">Array of objects containing metadata for the actions or functions specified in value.</returns>
+
+        var result = [];
+        for (var name in value) {
+            var i, len;
+            for (i = 0, len = value[name].length; i < len; i++) {
+                result.push(extend({ metadata: name }, value[name][i]));
+            }
+        }
+        return result;
+    };
+
+    var jsonApplyMetadata = function (value, metadata, dateParser, recognizeDates) {
+        /// <summary>Applies metadata coming from both the payload and the metadata object to the value.</summary>
+        /// <param name="value" type="Object">Data on which the metada is going to be applied.</param>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <param name="dateParser" type="function">Function used for parsing datetime values.</param>
+        /// <param name="recognizeDates" type="Boolean">
+        ///     True if strings formatted as datetime values should be treated as datetime values. False otherwise.
+        /// </param>
+        /// <returns type="Object">Transformed data.</returns>
+
+        if (value && typeof value === "object") {
+            var dataTypeName;
+            var valueMetadata = value.__metadata;
+
+            if (valueMetadata) {
+                if (valueMetadata.actions) {
+                    valueMetadata.actions = jsonReadAdvertisedActionsOrFunctions(valueMetadata.actions);
+                }
+                if (valueMetadata.functions) {
+                    valueMetadata.functions = jsonReadAdvertisedActionsOrFunctions(valueMetadata.functions);
+                }
+                dataTypeName = valueMetadata && valueMetadata.type;
+            }
+
+            var dataType = lookupEntityType(dataTypeName, metadata) || lookupComplexType(dataTypeName, metadata);
+            var propertyValue;
+            if (dataType) {
+                var properties = dataType.property;
+                if (properties) {
+                    var i, len;
+                    for (i = 0, len = properties.length; i < len; i++) {
+                        var property = properties[i];
+                        var propertyName = property.name;
+                        propertyValue = value[propertyName];
+
+                        if (property.type === "Edm.DateTime" || property.type === "Edm.DateTimeOffset") {
+                            if (propertyValue) {
+                                propertyValue = dateParser(propertyValue);
+                                if (!propertyValue) {
+                                    throw { message: "Invalid date/time value" };
+                                }
+                                value[propertyName] = propertyValue;
+                            }
+                        } else if (property.type === "Edm.Time") {
+                            value[propertyName] = parseDuration(propertyValue);
+                        }
+                    }
+                }
+            } else if (recognizeDates) {
+                for (var name in value) {
+                    propertyValue = value[name];
+                    if (typeof propertyValue === "string") {
+                        value[name] = dateParser(propertyValue) || propertyValue;
+                    }
+                }
+            }
+        }
+        return value;
+    };
+
+    var isJsonLight = function (contentType) {
+        /// <summary>Tests where the content type indicates a json light payload.</summary>
+        /// <param name="contentType">Object with media type and properties dictionary.</param>
+        /// <returns type="Boolean">True is the content type indicates a json light payload. False otherwise.</returns>
+
+        if (contentType) {
+            var odata = contentType.properties.odata;
+            return odata === "nometadata" || odata === "minimalmetadata" || odata === "fullmetadata";
+        }
+        return false;
+    };
+
+    var normalizeServiceDocument = function (data, baseURI) {
+        /// <summary>Normalizes a JSON service document to look like an ATOM service document.</summary>
+        /// <param name="data" type="Object">Object representation of service documents as deserialized.</param>
+        /// <param name="baseURI" type="String">Base URI to resolve relative URIs.</param>
+        /// <returns type="Object">An object representation of the service document.</returns>
+        var workspace = { collections: [] };
+
+        var i, len;
+        for (i = 0, len = data.EntitySets.length; i < len; i++) {
+            var title = data.EntitySets[i];
+            var collection = {
+                title: title,
+                href: normalizeURI(title, baseURI)
+            };
+
+            workspace.collections.push(collection);
+        }
+
+        return { workspaces: [workspace] };
+    };
+
+    // The regular expression corresponds to something like this:
+    // /Date(123+60)/
+    //
+    // This first number is date ticks, the + may be a - and is optional,
+    // with the second number indicating a timezone offset in minutes.
+    //
+    // On the wire, the leading and trailing forward slashes are
+    // escaped without being required to so the chance of collisions is reduced;
+    // however, by the time we see the objects, the characters already
+    // look like regular forward slashes.
+    var jsonDateRE = /^\/Date\((-?\d+)(\+|-)?(\d+)?\)\/$/;
+
+    var minutesToOffset = function (minutes) {
+        /// <summary>Formats the given minutes into (+/-)hh:mm format.</summary>
+        /// <param name="minutes" type="Number">Number of minutes to format.</param>
+        /// <returns type="String">The minutes in (+/-)hh:mm format.</returns>
+
+        var sign;
+        if (minutes < 0) {
+            sign = "-";
+            minutes = -minutes;
+        } else {
+            sign = "+";
+        }
+
+        var hours = Math.floor(minutes / 60);
+        minutes = minutes - (60 * hours);
+
+        return sign + formatNumberWidth(hours, 2) + ":" + formatNumberWidth(minutes, 2);
+    };
+
+    var parseJsonDateString = function (value) {
+        /// <summary>Parses the JSON Date representation into a Date object.</summary>
+        /// <param name="value" type="String">String value.</param>
+        /// <returns type="Date">A Date object if the value matches one; falsy otherwise.</returns>
+
+        var arr = value && jsonDateRE.exec(value);
+        if (arr) {
+            // 0 - complete results; 1 - ticks; 2 - sign; 3 - minutes
+            var result = new Date(parseInt10(arr[1]));
+            if (arr[2]) {
+                var mins = parseInt10(arr[3]);
+                if (arr[2] === "-") {
+                    mins = -mins;
+                }
+
+                // The offset is reversed to get back the UTC date, which is
+                // what the API will eventually have.
+                var current = result.getUTCMinutes();
+                result.setUTCMinutes(current - mins);
+                result.__edmType = "Edm.DateTimeOffset";
+                result.__offset = minutesToOffset(mins);
+            }
+            if (!isNaN(result.valueOf())) {
+                return result;
+            }
+        }
+
+        // Allow undefined to be returned.
+    };
+
+    // Some JSON implementations cannot produce the character sequence \/
+    // which is needed to format DateTime and DateTimeOffset into the
+    // JSON string representation defined by the OData protocol.
+    // See the history of this file for a candidate implementation of
+    // a 'formatJsonDateString' function.
+
+    var jsonParser = function (handler, text, context) {
+        /// <summary>Parses a JSON OData payload.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="text">Payload text (this parser also handles pre-parsed objects).</param>
+        /// <param name="context" type="Object">Object with parsing context.</param>
+        /// <returns>An object representation of the OData payload.</returns>
+
+        var recognizeDates = defined(context.recognizeDates, handler.recognizeDates);
+        var inferJsonLightFeedAsObject = defined(context.inferJsonLightFeedAsObject, handler.inferJsonLightFeedAsObject);
+        var model = context.metadata;
+        var dataServiceVersion = context.dataServiceVersion;
+        var dateParser = parseJsonDateString;
+        var json = (typeof text === "string") ? window.JSON.parse(text) : text;
+
+        if ((maxVersion("3.0", dataServiceVersion) === dataServiceVersion)) {
+            if (isJsonLight(context.contentType)) {
+                return jsonLightReadPayload(json, model, recognizeDates, inferJsonLightFeedAsObject, context.contentType.properties.odata);
+            }
+            dateParser = parseDateTime;
+        }
+
+        json = traverse(json.d, function (key, value) {
+            return jsonApplyMetadata(value, model, dateParser, recognizeDates);
+        });
+
+        json = jsonUpdateDataFromVersion(json, context.dataServiceVersion);
+        return jsonNormalizeData(json, context.response.requestUri);
+    };
+
+    var jsonToString = function (data) {
+        /// <summary>Converts the data into a JSON string.</summary>
+        /// <param name="data">Data to serialize.</param>
+        /// <returns type="String">The JSON string representation of data.</returns>
+
+        var result; // = undefined;
+        // Save the current date.toJSON function
+        var dateToJSON = Date.prototype.toJSON;
+        try {
+            // Set our own date.toJSON function
+            Date.prototype.toJSON = function () {
+                return formatDateTimeOffset(this);
+            };
+            result = window.JSON.stringify(data, jsonReplacer);
+        } finally {
+            // Restore the original toJSON function
+            Date.prototype.toJSON = dateToJSON;
+        }
+        return result;
+    };
+
+    var jsonSerializer = function (handler, data, context) {
+        /// <summary>Serializes the data by returning its string representation.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="data">Data to serialize.</param>
+        /// <param name="context" type="Object">Object with serialization context.</param>
+        /// <returns type="String">The string representation of data.</returns>
+
+        var dataServiceVersion = context.dataServiceVersion || "1.0";
+        var useJsonLight = defined(context.useJsonLight, handler.useJsonLight);
+        var cType = context.contentType = context.contentType || jsonContentType;
+
+        if (cType && cType.mediaType === jsonContentType.mediaType) {
+            var json = data;
+            if (useJsonLight || isJsonLight(cType)) {
+                context.dataServiceVersion = maxVersion(dataServiceVersion, "3.0");
+                json = formatJsonLight(data, context);
+                return jsonToString(json);
+            }
+            if (maxVersion("3.0", dataServiceVersion) === dataServiceVersion) {
+                cType.properties.odata = "verbose";
+                context.contentType = cType;
+            }
+            return jsonToString(json);
+        }
+        return undefined;
+    };
+
+    var jsonReplacer = function (_, value) {
+        /// <summary>JSON replacer function for converting a value to its JSON representation.</summary>
+        /// <param value type="Object">Value to convert.</param>
+        /// <returns type="String">JSON representation of the input value.</returns>
+        /// <remarks>
+        ///   This method is used during JSON serialization and invoked only by the JSON.stringify function.
+        ///   It should never be called directly.
+        /// </remarks>
+
+        if (value && value.__edmType === "Edm.Time") {
+            return formatDuration(value);
+        } else {
+            return value;
+        }
+    };
+
+    var jsonNormalizeData = function (data, baseURI) {
+        /// <summary>
+        /// Normalizes the specified data into an intermediate representation.
+        /// like the latest supported version.
+        /// </summary>
+        /// <param name="data" optional="false">Data to update.</param>
+        /// <param name="baseURI" optional="false">URI to use as the base for normalizing references.</param>
+
+        var isSvcDoc = isComplex(data) && !data.__metadata && isArray(data.EntitySets);
+        return isSvcDoc ? normalizeServiceDocument(data, baseURI) : data;
+    };
+
+    var jsonUpdateDataFromVersion = function (data, dataVersion) {
+        /// <summary>
+        /// Updates the specified data in the specified version to look
+        /// like the latest supported version.
+        /// </summary>
+        /// <param name="data" optional="false">Data to update.</param>
+        /// <param name="dataVersion" optional="true" type="String">Version the data is in (possibly unknown).</param>
+
+        // Strip the trailing comma if there.
+        if (dataVersion && dataVersion.lastIndexOf(";") === dataVersion.length - 1) {
+            dataVersion = dataVersion.substr(0, dataVersion.length - 1);
+        }
+
+        if (!dataVersion || dataVersion === "1.0") {
+            if (isArray(data)) {
+                data = { results: data };
+            }
+        }
+
+        return data;
+    };
+
+    var jsonHandler = handler(jsonParser, jsonSerializer, jsonMediaType, MAX_DATA_SERVICE_VERSION);
+    jsonHandler.recognizeDates = false;
+    jsonHandler.useJsonLight = false;
+    jsonHandler.inferJsonLightFeedAsObject = false;
+
+    odata.jsonHandler = jsonHandler;
+
+
+
+    // DATAJS INTERNAL START
+    odata.jsonParser = jsonParser;
+    odata.jsonSerializer = jsonSerializer;
+    odata.jsonNormalizeData = jsonNormalizeData;
+    odata.jsonUpdateDataFromVersion = jsonUpdateDataFromVersion;
+    odata.normalizeServiceDocument = normalizeServiceDocument;
+    odata.parseJsonDateString = parseJsonDateString;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
+
+
+
+
diff --git a/JSLib/src/odata-metadata.js b/JSLib/src/odata-metadata.js
new file mode 100644
index 0000000..a7ca00a
--- /dev/null
+++ b/JSLib/src/odata-metadata.js
@@ -0,0 +1,500 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-metadata.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // imports 
+
+    var contains = datajs.contains;
+    var normalizeURI = datajs.normalizeURI;
+    var xmlAttributes = datajs.xmlAttributes;
+    var xmlChildElements = datajs.xmlChildElements;
+    var xmlFirstChildElement = datajs.xmlFirstChildElement;
+    var xmlInnerText = datajs.xmlInnerText;
+    var xmlLocalName = datajs.xmlLocalName;
+    var xmlNamespaceURI = datajs.xmlNamespaceURI;
+    var xmlNS = datajs.xmlNS;
+    var xmlnsNS = datajs.xmlnsNS;
+    var xmlParse = datajs.xmlParse;
+
+    var createAttributeExtension = odata.createAttributeExtension;
+    var createElementExtension = odata.createElementExtension;
+    var edmxNs = odata.edmxNs;
+    var edmNs1 = odata.edmNs1;
+    var edmNs1_1 = odata.edmNs1_1;
+    var edmNs1_2 = odata.edmNs1_2;
+    var edmNs2a = odata.edmNs2a;
+    var edmNs2b = odata.edmNs2b;
+    var edmNs3 = odata.edmNs3;
+    var handler = odata.handler;
+    var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
+    var odataMetaXmlNs = odata.odataMetaXmlNs;
+
+
+    var xmlMediaType = "application/xml";
+
+    // CONTENT START
+
+    var schemaElement = function (attributes, elements, text, ns) {
+        /// <summary>Creates an object that describes an element in an schema.</summary>
+        /// <param name="attributes" type="Array">List containing the names of the attributes allowed for this element.</param>
+        /// <param name="elements" type="Array">List containing the names of the child elements allowed for this element.</param>
+        /// <param name="text" type="Boolean">Flag indicating if the element's text value is of interest or not.</param>
+        /// <param name="ns" type="String">Namespace to which the element belongs to.</param>
+        /// <remarks>
+        ///    If a child element name ends with * then it is understood by the schema that that child element can appear 0 or more times.
+        /// </remarks>
+        /// <returns type="Object">Object with attributes, elements, text, and ns fields.</returns>
+
+        return {
+            attributes: attributes,
+            elements: elements,
+            text: text || false,
+            ns: ns
+        };
+    };
+
+    // It's assumed that all elements may have Documentation children and Annotation elements.
+    // See http://msdn.microsoft.com/en-us/library/bb399292.aspx for a CSDL reference.
+    var schema = {
+        elements: {
+            Annotations: schemaElement(
+            /*attributes*/["Target", "Qualifier"],
+            /*elements*/["TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            Association: schemaElement(
+            /*attributes*/["Name"],
+            /*elements*/["End*", "ReferentialConstraint", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            AssociationSet: schemaElement(
+            /*attributes*/["Name", "Association"],
+            /*elements*/["End*", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            Binary: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Bool: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Collection: schemaElement(
+            /*attributes*/null,
+            /*elements*/["String*", "Int*", "Float*", "Decimal*", "Bool*", "DateTime*", "DateTimeOffset*", "Guid*", "Binary*", "Time*", "Collection*", "Record*"]
+            ),
+            CollectionType: schemaElement(
+            /*attributes*/["ElementType", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "SRID"],
+            /*elements*/["CollectionType", "ReferenceType", "RowType", "TypeRef"]
+            ),
+            ComplexType: schemaElement(
+            /*attributes*/["Name", "BaseType", "Abstract"],
+            /*elements*/["Property*", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            DateTime: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            DateTimeOffset: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Decimal: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            DefiningExpression: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Dependent: schemaElement(
+            /*attributes*/["Role"],
+            /*elements*/["PropertyRef*"]
+            ),
+            Documentation: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            End: schemaElement(
+            /*attributes*/["Type", "Role", "Multiplicity", "EntitySet"],
+            /*elements*/["OnDelete"]
+            ),
+            EntityContainer: schemaElement(
+            /*attributes*/["Name", "Extends"],
+            /*elements*/["EntitySet*", "AssociationSet*", "FunctionImport*", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            EntitySet: schemaElement(
+            /*attributes*/["Name", "EntityType"],
+            /*elements*/["TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            EntityType: schemaElement(
+            /*attributes*/["Name", "BaseType", "Abstract", "OpenType"],
+            /*elements*/["Key", "Property*", "NavigationProperty*", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            EnumType: schemaElement(
+            /*attributes*/["Name", "UnderlyingType", "IsFlags"],
+            /*elements*/["Member*"]
+            ),
+            Float: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Function: schemaElement(
+            /*attributes*/["Name", "ReturnType"],
+            /*elements*/["Parameter*", "DefiningExpression", "ReturnType", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            FunctionImport: schemaElement(
+            /*attributes*/["Name", "ReturnType", "EntitySet", "IsSideEffecting", "IsComposable", "IsBindable", "EntitySetPath"],
+            /*elements*/["Parameter*", "ReturnType", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            Guid: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Int: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Key: schemaElement(
+            /*attributes*/null,
+            /*elements*/["PropertyRef*"]
+            ),
+            LabeledElement: schemaElement(
+            /*attributes*/["Name"],
+            /*elements*/["Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time", "Collection", "Record", "LabeledElement", "Null"]
+            ),
+            Member: schemaElement(
+            /*attributes*/["Name", "Value"]
+            ),
+            NavigationProperty: schemaElement(
+            /*attributes*/["Name", "Relationship", "ToRole", "FromRole", "ContainsTarget"],
+            /*elements*/["TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            Null: schemaElement(
+            /*attributes*/null,
+            /*elements*/null
+            ),
+            OnDelete: schemaElement(
+            /*attributes*/["Action"]
+            ),
+            Path: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Parameter: schemaElement(
+            /*attributes*/["Name", "Type", "Mode", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "ConcurrencyMode", "SRID"],
+            /*elements*/["CollectionType", "ReferenceType", "RowType", "TypeRef", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            Principal: schemaElement(
+            /*attributes*/["Role"],
+            /*elements*/["PropertyRef*"]
+            ),
+            Property: schemaElement(
+            /*attributes*/["Name", "Type", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "ConcurrencyMode", "CollectionKind", "SRID"],
+            /*elements*/["CollectionType", "ReferenceType", "RowType", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            PropertyRef: schemaElement(
+            /*attributes*/["Name"]
+            ),
+            PropertyValue: schemaElement(
+            /*attributes*/["Property", "Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time"],
+            /*Elements*/["Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time", "Collection", "Record", "LabeledElement", "Null"]
+            ),
+            ReferenceType: schemaElement(
+            /*attributes*/["Type"]
+            ),
+            ReferentialConstraint: schemaElement(
+            /*attributes*/null,
+            /*elements*/["Principal", "Dependent"]
+            ),
+            ReturnType: schemaElement(
+            /*attributes*/["ReturnType", "Type", "EntitySet"],
+            /*elements*/["CollectionType", "ReferenceType", "RowType"]
+            ),
+            RowType: schemaElement(
+            /*elements*/["Property*"]
+            ),
+            String: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Schema: schemaElement(
+            /*attributes*/["Namespace", "Alias"],
+            /*elements*/["Using*", "EntityContainer*", "EntityType*", "Association*", "ComplexType*", "Function*", "ValueTerm*", "Annotations*"]
+            ),
+            Time: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            TypeAnnotation: schemaElement(
+            /*attributes*/["Term", "Qualifier"],
+            /*elements*/["PropertyValue*"]
+            ),
+            TypeRef: schemaElement(
+            /*attributes*/["Type", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "SRID"]
+            ),
+            Using: schemaElement(
+            /*attributes*/["Namespace", "Alias"]
+            ),
+            ValueAnnotation: schemaElement(
+            /*attributes*/["Term", "Qualifier", "Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time"],
+            /*Elements*/["Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time", "Collection", "Record", "LabeledElement", "Null"]
+            ),
+            ValueTerm: schemaElement(
+            /*attributes*/["Name", "Type"],
+            /*elements*/["TypeAnnotation*", "ValueAnnotation*"]
+            ),
+
+            // See http://msdn.microsoft.com/en-us/library/dd541238(v=prot.10) for an EDMX reference.
+            Edmx: schemaElement(
+            /*attributes*/["Version"],
+            /*elements*/["DataServices", "Reference*", "AnnotationsReference*"],
+            /*text*/false,
+            /*ns*/edmxNs
+            ),
+            DataServices: schemaElement(
+            /*attributes*/null,
+            /*elements*/["Schema*"],
+            /*text*/false,
+            /*ns*/edmxNs
+            )
+        }
+    };
+
+    // See http://msdn.microsoft.com/en-us/library/ee373839.aspx for a feed customization reference.
+    var customizationAttributes = ["m:FC_ContentKind", "m:FC_KeepInContent", "m:FC_NsPrefix", "m:FC_NsUri", "m:FC_SourcePath", "m:FC_TargetPath"];
+    schema.elements.Property.attributes = schema.elements.Property.attributes.concat(customizationAttributes);
+    schema.elements.EntityType.attributes = schema.elements.EntityType.attributes.concat(customizationAttributes);
+
+    // See http://msdn.microsoft.com/en-us/library/dd541284(PROT.10).aspx for an EDMX reference.
+    schema.elements.Edmx = { attributes: ["Version"], elements: ["DataServices"], ns: edmxNs };
+    schema.elements.DataServices = { elements: ["Schema*"], ns: edmxNs };
+
+    // See http://msdn.microsoft.com/en-us/library/dd541233(v=PROT.10) for Conceptual Schema Definition Language Document for Data Services.
+    schema.elements.EntityContainer.attributes.push("m:IsDefaultEntityContainer");
+    schema.elements.Property.attributes.push("m:MimeType");
+    schema.elements.FunctionImport.attributes.push("m:HttpMethod");
+    schema.elements.FunctionImport.attributes.push("m:IsAlwaysBindable");
+    schema.elements.EntityType.attributes.push("m:HasStream");
+    schema.elements.DataServices.attributes = ["m:DataServiceVersion", "m:MaxDataServiceVersion"];
+
+    var scriptCase = function (text) {
+        /// <summary>Converts a Pascal-case identifier into a camel-case identifier.</summary>
+        /// <param name="text" type="String">Text to convert.</param>
+        /// <returns type="String">Converted text.</returns>
+        /// <remarks>If the text starts with multiple uppercase characters, it is left as-is.</remarks>
+
+        if (!text) {
+            return text;
+        }
+
+        if (text.length > 1) {
+            var firstTwo = text.substr(0, 2);
+            if (firstTwo === firstTwo.toUpperCase()) {
+                return text;
+            }
+
+            return text.charAt(0).toLowerCase() + text.substr(1);
+        }
+
+        return text.charAt(0).toLowerCase();
+    };
+
+    var getChildSchema = function (parentSchema, candidateName) {
+        /// <summary>Gets the schema node for the specified element.</summary>
+        /// <param name="parentSchema" type="Object">Schema of the parent XML node of 'element'.</param>
+        /// <param name="candidateName">XML element name to consider.</param>
+        /// <returns type="Object">The schema that describes the specified element; null if not found.</returns>
+
+        if (candidateName === "Documentation") {
+            return { isArray: true, propertyName: "documentation" };
+        }
+
+        var elements = parentSchema.elements;
+        if (!elements) {
+            return null;
+        }
+
+        var i, len;
+        for (i = 0, len = elements.length; i < len; i++) {
+            var elementName = elements[i];
+            var multipleElements = false;
+            if (elementName.charAt(elementName.length - 1) === "*") {
+                multipleElements = true;
+                elementName = elementName.substr(0, elementName.length - 1);
+            }
+
+            if (candidateName === elementName) {
+                var propertyName = scriptCase(elementName);
+                return { isArray: multipleElements, propertyName: propertyName };
+            }
+        }
+
+        return null;
+    };
+
+    // This regular expression is used to detect a feed customization element
+    // after we've normalized it into the 'm' prefix. It starts with m:FC_,
+    // followed by other characters, and ends with _ and a number.
+    // The captures are 0 - whole string, 1 - name as it appears in internal table.
+    var isFeedCustomizationNameRE = /^(m:FC_.*)_[0-9]+$/;
+
+    var isEdmNamespace = function (nsURI) {
+        /// <summary>Checks whether the specifies namespace URI is one of the known CSDL namespace URIs.</summary>
+        /// <param name="nsURI" type="String">Namespace URI to check.</param>
+        /// <returns type="Boolean">true if nsURI is a known CSDL namespace; false otherwise.</returns>
+
+        return nsURI === edmNs1 ||
+               nsURI === edmNs1_1 ||
+               nsURI === edmNs1_2 ||
+               nsURI === edmNs2a ||
+               nsURI === edmNs2b ||
+               nsURI === edmNs3;
+    };
+
+    var parseConceptualModelElement = function (element) {
+        /// <summary>Parses a CSDL document.</summary>
+        /// <param name="element">DOM element to parse.</param>
+        /// <returns type="Object">An object describing the parsed element.</returns>
+
+        var localName = xmlLocalName(element);
+        var nsURI = xmlNamespaceURI(element);
+        var elementSchema = schema.elements[localName];
+        if (!elementSchema) {
+            return null;
+        }
+
+        if (elementSchema.ns) {
+            if (nsURI !== elementSchema.ns) {
+                return null;
+            }
+        } else if (!isEdmNamespace(nsURI)) {
+            return null;
+        }
+
+        var item = {};
+        var extensions = [];
+        var attributes = elementSchema.attributes || [];
+        xmlAttributes(element, function (attribute) {
+
+            var localName = xmlLocalName(attribute);
+            var nsURI = xmlNamespaceURI(attribute);
+            var value = attribute.value;
+
+            // Don't do anything with xmlns attributes.
+            if (nsURI === xmlnsNS) {
+                return;
+            }
+
+            // Currently, only m: for metadata is supported as a prefix in the internal schema table,
+            // un-prefixed element names imply one a CSDL element.
+            var schemaName = null;
+            var handled = false;
+            if (isEdmNamespace(nsURI) || nsURI === null) {
+                schemaName = "";
+            } else if (nsURI === odataMetaXmlNs) {
+                schemaName = "m:";
+            }
+
+            if (schemaName !== null) {
+                schemaName += localName;
+
+                // Feed customizations for complex types have additional
+                // attributes with a suffixed counter starting at '1', so
+                // take that into account when doing the lookup.
+                var match = isFeedCustomizationNameRE.exec(schemaName);
+                if (match) {
+                    schemaName = match[1];
+                }
+
+                if (contains(attributes, schemaName)) {
+                    handled = true;
+                    item[scriptCase(localName)] = value;
+                }
+            }
+
+            if (!handled) {
+                extensions.push(createAttributeExtension(attribute));
+            }
+        });
+
+        xmlChildElements(element, function (child) {
+            var localName = xmlLocalName(child);
+            var childSchema = getChildSchema(elementSchema, localName);
+            if (childSchema) {
+                if (childSchema.isArray) {
+                    var arr = item[childSchema.propertyName];
+                    if (!arr) {
+                        arr = [];
+                        item[childSchema.propertyName] = arr;
+                    }
+                    arr.push(parseConceptualModelElement(child));
+                } else {
+                    item[childSchema.propertyName] = parseConceptualModelElement(child);
+                }
+            } else {
+                extensions.push(createElementExtension(child));
+            }
+        });
+
+        if (elementSchema.text) {
+            item.text = xmlInnerText(element);
+        }
+
+        if (extensions.length) {
+            item.extensions = extensions;
+        }
+
+        return item;
+    };
+
+    var metadataParser = function (handler, text) {
+        /// <summary>Parses a metadata document.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="text" type="String">Metadata text.</param>
+        /// <returns>An object representation of the conceptual model.</returns>
+
+        var doc = xmlParse(text);
+        var root = xmlFirstChildElement(doc);
+        return parseConceptualModelElement(root) || undefined;
+    };
+
+    odata.metadataHandler = handler(metadataParser, null, xmlMediaType, MAX_DATA_SERVICE_VERSION);
+
+    // DATAJS INTERNAL START
+    odata.schema = schema;
+    odata.scriptCase = scriptCase;
+    odata.getChildSchema = getChildSchema;
+    odata.parseConceptualModelElement = parseConceptualModelElement;
+    odata.metadataParser = metadataParser;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/odata-net.js b/JSLib/src/odata-net.js
new file mode 100644
index 0000000..ad9b8dd
--- /dev/null
+++ b/JSLib/src/odata-net.js
@@ -0,0 +1,330 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-net.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports.
+
+    var defined = datajs.defined;
+    var delay = datajs.delay;
+
+    // CONTENT START
+    var ticks = 0;
+
+    var canUseJSONP = function (request) {
+        /// <summary>
+        /// Checks whether the specified request can be satisfied with a JSONP request.
+        /// </summary>
+        /// <param name="request">Request object to check.</param>
+        /// <returns type="Boolean">true if the request can be satisfied; false otherwise.</returns>
+
+        // Requests that 'degrade' without changing their meaning by going through JSONP
+        // are considered usable.
+        //
+        // We allow data to come in a different format, as the servers SHOULD honor the Accept
+        // request but may in practice return content with a different MIME type.
+        if (request.method && request.method !== "GET") {
+            return false;
+        }
+
+        return true;
+    };
+
+    var createIFrame = function (url) {
+        /// <summary>Creates an IFRAME tag for loading the JSONP script</summary>
+        /// <param name="url" type="String">The source URL of the script</param>
+        /// <returns type="HTMLElement">The IFRAME tag</returns>
+        var iframe = window.document.createElement("IFRAME");
+        iframe.style.display = "none";
+
+        var attributeEncodedUrl = url.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/\</g, "&lt;");
+        var html = "<html><head><script type=\"text/javascript\" src=\"" + attributeEncodedUrl + "\"><\/script><\/head><body><\/body><\/html>";
+
+        var body = window.document.getElementsByTagName("BODY")[0];
+        body.appendChild(iframe);
+
+        writeHtmlToIFrame(iframe, html);
+        return iframe;
+    };
+
+    var createXmlHttpRequest = function () {
+        /// <summary>Creates a XmlHttpRequest object.</summary>
+        /// <returns type="XmlHttpRequest">XmlHttpRequest object.</returns>
+        if (window.XMLHttpRequest) {
+            return new window.XMLHttpRequest();
+        }
+        var exception;
+        if (window.ActiveXObject) {
+            try {
+                return new window.ActiveXObject("Msxml2.XMLHTTP.6.0");
+            } catch (_) {
+                try {
+                    return new window.ActiveXObject("Msxml2.XMLHTTP.3.0");
+                } catch (e) {
+                    exception = e;
+                }
+            }
+        } else {
+            exception = { message: "XMLHttpRequest not supported" };
+        }
+        throw exception;
+    };
+
+    var isAbsoluteUrl = function (url) {
+        /// <summary>Checks whether the specified URL is an absolute URL.</summary>
+        /// <param name="url" type="String">URL to check.</param>
+        /// <returns type="Boolean">true if the url is an absolute URL; false otherwise.</returns>
+
+        return url.indexOf("http://") === 0 ||
+            url.indexOf("https://") === 0 ||
+            url.indexOf("file://") === 0;
+    };
+
+    var isLocalUrl = function (url) {
+        /// <summary>Checks whether the specified URL is local to the current context.</summary>
+        /// <param name="url" type="String">URL to check.</param>
+        /// <returns type="Boolean">true if the url is a local URL; false otherwise.</returns>
+
+        if (!isAbsoluteUrl(url)) {
+            return true;
+        }
+
+        // URL-embedded username and password will not be recognized as same-origin URLs.
+        var location = window.location;
+        var locationDomain = location.protocol + "//" + location.host + "/";
+        return (url.indexOf(locationDomain) === 0);
+    };
+
+    var removeCallback = function (name, tick) {
+        /// <summary>Removes a callback used for a JSONP request.</summary>
+        /// <param name="name" type="String">Function name to remove.</param>
+        /// <param name="tick" type="Number">Tick count used on the callback.</param>
+        try {
+            delete window[name];
+        } catch (err) {
+            window[name] = undefined;
+            if (tick === ticks - 1) {
+                ticks -= 1;
+            }
+        }
+    };
+
+    var removeIFrame = function (iframe) {
+        /// <summary>Removes an iframe.</summary>
+        /// <param name="iframe" type="Object">The iframe to remove.</param>
+        /// <returns type="Object">Null value to be assigned to iframe reference.</returns>
+        if (iframe) {
+            writeHtmlToIFrame(iframe, "");
+            iframe.parentNode.removeChild(iframe);
+        }
+
+        return null;
+    };
+
+    var readResponseHeaders = function (xhr, headers) {
+        /// <summary>Reads response headers into array.</summary>
+        /// <param name="xhr" type="XMLHttpRequest">HTTP request with response available.</param>
+        /// <param name="headers" type="Array">Target array to fill with name/value pairs.</param>
+
+        var responseHeaders = xhr.getAllResponseHeaders().split(/\r?\n/);
+        var i, len;
+        for (i = 0, len = responseHeaders.length; i < len; i++) {
+            if (responseHeaders[i]) {
+                var header = responseHeaders[i].split(": ");
+                headers[header[0]] = header[1];
+            }
+        }
+    };
+
+    var writeHtmlToIFrame = function (iframe, html) {
+        /// <summary>Writes HTML to an IFRAME document.</summary>
+        /// <param name="iframe" type="HTMLElement">The IFRAME element to write to.</param>
+        /// <param name="html" type="String">The HTML to write.</param>
+        var frameDocument = (iframe.contentWindow) ? iframe.contentWindow.document : iframe.contentDocument.document;
+        frameDocument.open();
+        frameDocument.write(html);
+        frameDocument.close();
+    };
+
+    odata.defaultHttpClient = {
+        callbackParameterName: "$callback",
+
+        formatQueryString: "$format=json",
+
+        enableJsonpCallback: false,
+
+        request: function (request, success, error) {
+            /// <summary>Performs a network request.</summary>
+            /// <param name="request" type="Object">Request description.</request>
+            /// <param name="success" type="Function">Success callback with the response object.</param>
+            /// <param name="error" type="Function">Error callback with an error object.</param>
+            /// <returns type="Object">Object with an 'abort' method for the operation.</returns>
+
+            var result = {};
+            var xhr = null;
+            var done = false;
+            var iframe;
+
+            result.abort = function () {
+                iframe = removeIFrame(iframe);
+                if (done) {
+                    return;
+                }
+
+                done = true;
+                if (xhr) {
+                    xhr.abort();
+                    xhr = null;
+                }
+
+                error({ message: "Request aborted" });
+            };
+
+            var handleTimeout = function () {
+                iframe = removeIFrame(iframe);
+                if (!done) {
+                    done = true;
+                    xhr = null;
+                    error({ message: "Request timed out" });
+                }
+            };
+
+            var name;
+            var url = request.requestUri;
+            var enableJsonpCallback = defined(request.enableJsonpCallback, this.enableJsonpCallback);
+            var callbackParameterName = defined(request.callbackParameterName, this.callbackParameterName);
+            var formatQueryString = defined(request.formatQueryString, this.formatQueryString);
+            if (!enableJsonpCallback || isLocalUrl(url)) {
+
+                xhr = createXmlHttpRequest();
+                xhr.onreadystatechange = function () {
+                    if (done || xhr === null || xhr.readyState !== 4) {
+                        return;
+                    }
+
+                    // Workaround for XHR behavior on IE.
+                    var statusText = xhr.statusText;
+                    var statusCode = xhr.status;
+                    if (statusCode === 1223) {
+                        statusCode = 204;
+                        statusText = "No Content";
+                    }
+
+                    var headers = [];
+                    readResponseHeaders(xhr, headers);
+
+                    var response = { requestUri: url, statusCode: statusCode, statusText: statusText, headers: headers, body: xhr.responseText };
+
+                    done = true;
+                    xhr = null;
+                    if (statusCode >= 200 && statusCode <= 299) {
+                        success(response);
+                    } else {
+                        error({ message: "HTTP request failed", request: request, response: response });
+                    }
+                };
+
+                xhr.open(request.method || "GET", url, true, request.user, request.password);
+
+                // Set the name/value pairs.
+                if (request.headers) {
+                    for (name in request.headers) {
+                        xhr.setRequestHeader(name, request.headers[name]);
+                    }
+                }
+
+                // Set the timeout if available.
+                if (request.timeoutMS) {
+                    xhr.timeout = request.timeoutMS;
+                    xhr.ontimeout = handleTimeout;
+                }
+
+                xhr.send(request.body);
+            } else {
+                if (!canUseJSONP(request)) {
+                    throw { message: "Request is not local and cannot be done through JSONP." };
+                }
+
+                var tick = ticks;
+                ticks += 1;
+                var tickText = tick.toString();
+                var succeeded = false;
+                var timeoutId;
+                name = "handleJSONP_" + tickText;
+                window[name] = function (data) {
+                    iframe = removeIFrame(iframe);
+                    if (!done) {
+                        succeeded = true;
+                        window.clearTimeout(timeoutId);
+                        removeCallback(name, tick);
+
+                        // Workaround for IE8 and IE10 below where trying to access data.constructor after the IFRAME has been removed
+                        // throws an "unknown exception"
+                        if (window.ActiveXObject) {
+                            data = window.JSON.parse(window.JSON.stringify(data));
+                        }
+
+
+                        var headers;
+                        // Adding dataServiceVersion in case of json light ( data.d doesn't exist )
+                        if (data.d === undefined) {
+                            headers = { "Content-Type": "application/json;odata=minimalmetadata", dataServiceVersion: "3.0" };
+                        } else {
+                            headers = { "Content-Type": "application/json" };
+                        }
+                        // Call the success callback in the context of the parent window, instead of the IFRAME
+                        delay(function () {
+                            removeIFrame(iframe);
+                            success({ body: data, statusCode: 200, headers: headers });
+                        });
+                    }
+                };
+
+                // Default to two minutes before timing out, 1000 ms * 60 * 2 = 120000.
+                var timeoutMS = (request.timeoutMS) ? request.timeoutMS : 120000;
+                timeoutId = window.setTimeout(handleTimeout, timeoutMS);
+
+                var queryStringParams = callbackParameterName + "=parent." + name;
+                if (this.formatQueryString) {
+                    queryStringParams += "&" + formatQueryString;
+                }
+
+                var qIndex = url.indexOf("?");
+                if (qIndex === -1) {
+                    url = url + "?" + queryStringParams;
+                } else if (qIndex === url.length - 1) {
+                    url = url + queryStringParams;
+                } else {
+                    url = url + "&" + queryStringParams;
+                }
+
+                iframe = createIFrame(url);
+            }
+
+            return result;
+        }
+    };
+
+    // DATAJS INTERNAL START
+    odata.canUseJSONP = canUseJSONP;
+    odata.isAbsoluteUrl = isAbsoluteUrl;
+    odata.isLocalUrl = isLocalUrl;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/odata-utils.js b/JSLib/src/odata-utils.js
new file mode 100644
index 0000000..ad2cf00
--- /dev/null
+++ b/JSLib/src/odata-utils.js
@@ -0,0 +1,1117 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-utils.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports
+    var assigned = datajs.assigned;
+    var contains = datajs.contains;
+    var find = datajs.find;
+    var isArray = datajs.isArray;
+    var isDate = datajs.isDate;
+    var isObject = datajs.isObject;
+    var parseInt10 = datajs.parseInt10;
+
+    // CONTENT START
+
+    var dataItemTypeName = function (value, metadata) {
+        /// <summary>Gets the type name of a data item value that belongs to a feed, an entry, a complex type property, or a collection property.</summary>
+        /// <param name="value">Value of the data item from which the type name is going to be retrieved.</param>
+        /// <param name="metadata" type="object" optional="true">Object containing metadata about the data tiem.</param>
+        /// <remarks>
+        ///    This function will first try to get the type name from the data item's value itself if it is an object with a __metadata property; otherwise
+        ///    it will try to recover it from the metadata.  If both attempts fail, it will return null.
+        /// </remarks>
+        /// <returns type="String">Data item type name; null if the type name cannot be found within the value or the metadata</returns>
+
+        var valueTypeName = ((value && value.__metadata) || {}).type;
+        return valueTypeName || (metadata ? metadata.type : null);
+    };
+
+    var EDM = "Edm.";
+    var EDM_BINARY = EDM + "Binary";
+    var EDM_BOOLEAN = EDM + "Boolean";
+    var EDM_BYTE = EDM + "Byte";
+    var EDM_DATETIME = EDM + "DateTime";
+    var EDM_DATETIMEOFFSET = EDM + "DateTimeOffset";
+    var EDM_DECIMAL = EDM + "Decimal";
+    var EDM_DOUBLE = EDM + "Double";
+    var EDM_GUID = EDM + "Guid";
+    var EDM_INT16 = EDM + "Int16";
+    var EDM_INT32 = EDM + "Int32";
+    var EDM_INT64 = EDM + "Int64";
+    var EDM_SBYTE = EDM + "SByte";
+    var EDM_SINGLE = EDM + "Single";
+    var EDM_STRING = EDM + "String";
+    var EDM_TIME = EDM + "Time";
+
+    var EDM_GEOGRAPHY = EDM + "Geography";
+    var EDM_GEOGRAPHY_POINT = EDM_GEOGRAPHY + "Point";
+    var EDM_GEOGRAPHY_LINESTRING = EDM_GEOGRAPHY + "LineString";
+    var EDM_GEOGRAPHY_POLYGON = EDM_GEOGRAPHY + "Polygon";
+    var EDM_GEOGRAPHY_COLLECTION = EDM_GEOGRAPHY + "Collection";
+    var EDM_GEOGRAPHY_MULTIPOLYGON = EDM_GEOGRAPHY + "MultiPolygon";
+    var EDM_GEOGRAPHY_MULTILINESTRING = EDM_GEOGRAPHY + "MultiLineString";
+    var EDM_GEOGRAPHY_MULTIPOINT = EDM_GEOGRAPHY + "MultiPoint";
+
+    var EDM_GEOMETRY = EDM + "Geometry";
+    var EDM_GEOMETRY_POINT = EDM_GEOMETRY + "Point";
+    var EDM_GEOMETRY_LINESTRING = EDM_GEOMETRY + "LineString";
+    var EDM_GEOMETRY_POLYGON = EDM_GEOMETRY + "Polygon";
+    var EDM_GEOMETRY_COLLECTION = EDM_GEOMETRY + "Collection";
+    var EDM_GEOMETRY_MULTIPOLYGON = EDM_GEOMETRY + "MultiPolygon";
+    var EDM_GEOMETRY_MULTILINESTRING = EDM_GEOMETRY + "MultiLineString";
+    var EDM_GEOMETRY_MULTIPOINT = EDM_GEOMETRY + "MultiPoint";
+
+    var GEOJSON_POINT = "Point";
+    var GEOJSON_LINESTRING = "LineString";
+    var GEOJSON_POLYGON = "Polygon";
+    var GEOJSON_MULTIPOINT = "MultiPoint";
+    var GEOJSON_MULTILINESTRING = "MultiLineString";
+    var GEOJSON_MULTIPOLYGON = "MultiPolygon";
+    var GEOJSON_GEOMETRYCOLLECTION = "GeometryCollection";
+
+    var primitiveEdmTypes = [
+        EDM_STRING,
+        EDM_INT32,
+        EDM_INT64,
+        EDM_BOOLEAN,
+        EDM_DOUBLE,
+        EDM_SINGLE,
+        EDM_DATETIME,
+        EDM_DATETIMEOFFSET,
+        EDM_TIME,
+        EDM_DECIMAL,
+        EDM_GUID,
+        EDM_BYTE,
+        EDM_INT16,
+        EDM_SBYTE,
+        EDM_BINARY
+    ];
+
+    var geometryEdmTypes = [
+        EDM_GEOMETRY,
+        EDM_GEOMETRY_POINT,
+        EDM_GEOMETRY_LINESTRING,
+        EDM_GEOMETRY_POLYGON,
+        EDM_GEOMETRY_COLLECTION,
+        EDM_GEOMETRY_MULTIPOLYGON,
+        EDM_GEOMETRY_MULTILINESTRING,
+        EDM_GEOMETRY_MULTIPOINT
+    ];
+
+    var geographyEdmTypes = [
+        EDM_GEOGRAPHY,
+        EDM_GEOGRAPHY_POINT,
+        EDM_GEOGRAPHY_LINESTRING,
+        EDM_GEOGRAPHY_POLYGON,
+        EDM_GEOGRAPHY_COLLECTION,
+        EDM_GEOGRAPHY_MULTIPOLYGON,
+        EDM_GEOGRAPHY_MULTILINESTRING,
+        EDM_GEOGRAPHY_MULTIPOINT
+    ];
+
+    var forEachSchema = function (metadata, callback) {
+        /// <summary>Invokes a function once per schema in metadata.</summary>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <param name="callback" type="Function">Callback function to invoke once per schema.</param>
+        /// <returns>
+        /// The first truthy value to be returned from the callback; null or the last falsy value otherwise.
+        /// </returns>
+
+        if (!metadata) {
+            return null;
+        }
+
+        if (isArray(metadata)) {
+            var i, len, result;
+            for (i = 0, len = metadata.length; i < len; i++) {
+                result = forEachSchema(metadata[i], callback);
+                if (result) {
+                    return result;
+                }
+            }
+
+            return null;
+        } else {
+            if (metadata.dataServices) {
+                return forEachSchema(metadata.dataServices.schema, callback);
+            }
+
+            return callback(metadata);
+        }
+    };
+
+    var formatMilliseconds = function (ms, ns) {
+        /// <summary>Formats a millisecond and a nanosecond value into a single string.</summary>
+        /// <param name="ms" type="Number" mayBeNull="false">Number of milliseconds to format.</param>
+        /// <param name="ns" type="Number" mayBeNull="false">Number of nanoseconds to format.</param>
+        /// <returns type="String">Formatted text.</returns>
+        /// <remarks>If the value is already as string it's returned as-is.</remarks>
+
+        // Avoid generating milliseconds if not necessary.
+        if (ms === 0) {
+            ms = "";
+        } else {
+            ms = "." + formatNumberWidth(ms.toString(), 3);
+        }
+        if (ns > 0) {
+            if (ms === "") {
+                ms = ".000";
+            }
+            ms += formatNumberWidth(ns.toString(), 4);
+        }
+        return ms;
+    };
+
+    var formatDateTimeOffset = function (value) {
+        /// <summary>Formats a DateTime or DateTimeOffset value a string.</summary>
+        /// <param name="value" type="Date" mayBeNull="false">Value to format.</param>
+        /// <returns type="String">Formatted text.</returns>
+        /// <remarks>If the value is already as string it's returned as-is.</remarks>
+
+        if (typeof value === "string") {
+            return value;
+        }
+
+        var hasOffset = isDateTimeOffset(value);
+        var offset = getCanonicalTimezone(value.__offset);
+        if (hasOffset && offset !== "Z") {
+            // We're about to change the value, so make a copy.
+            value = new Date(value.valueOf());
+
+            var timezone = parseTimezone(offset);
+            var hours = value.getUTCHours() + (timezone.d * timezone.h);
+            var minutes = value.getUTCMinutes() + (timezone.d * timezone.m);
+
+            value.setUTCHours(hours, minutes);
+        } else if (!hasOffset) {
+            // Don't suffix a 'Z' for Edm.DateTime values.
+            offset = "";
+        }
+
+        var year = value.getUTCFullYear();
+        var month = value.getUTCMonth() + 1;
+        var sign = "";
+        if (year <= 0) {
+            year = -(year - 1);
+            sign = "-";
+        }
+
+        var ms = formatMilliseconds(value.getUTCMilliseconds(), value.__ns);
+
+        return sign +
+            formatNumberWidth(year, 4) + "-" +
+            formatNumberWidth(month, 2) + "-" +
+            formatNumberWidth(value.getUTCDate(), 2) + "T" +
+            formatNumberWidth(value.getUTCHours(), 2) + ":" +
+            formatNumberWidth(value.getUTCMinutes(), 2) + ":" +
+            formatNumberWidth(value.getUTCSeconds(), 2) +
+            ms + offset;
+    };
+
+    var formatDuration = function (value) {
+        /// <summary>Converts a duration to a string in xsd:duration format.</summary>
+        /// <param name="value" type="Object">Object with ms and __edmType properties.</param>
+        /// <returns type="String">String representation of the time object in xsd:duration format.</returns>
+
+        var ms = value.ms;
+
+        var sign = "";
+        if (ms < 0) {
+            sign = "-";
+            ms = -ms;
+        }
+
+        var days = Math.floor(ms / 86400000);
+        ms -= 86400000 * days;
+        var hours = Math.floor(ms / 3600000);
+        ms -= 3600000 * hours;
+        var minutes = Math.floor(ms / 60000);
+        ms -= 60000 * minutes;
+        var seconds = Math.floor(ms / 1000);
+        ms -= seconds * 1000;
+
+        return sign + "P" +
+               formatNumberWidth(days, 2) + "DT" +
+               formatNumberWidth(hours, 2) + "H" +
+               formatNumberWidth(minutes, 2) + "M" +
+               formatNumberWidth(seconds, 2) +
+               formatMilliseconds(ms, value.ns) + "S";
+    };
+
+    var formatNumberWidth = function (value, width, append) {
+        /// <summary>Formats the specified value to the given width.</summary>
+        /// <param name="value" type="Number">Number to format (non-negative).</param>
+        /// <param name="width" type="Number">Minimum width for number.</param>
+        /// <param name="append" type="Boolean">Flag indicating if the value is padded at the beginning (false) or at the end (true).</param>
+        /// <returns type="String">Text representation.</returns>
+        var result = value.toString(10);
+        while (result.length < width) {
+            if (append) {
+                result += "0";
+            } else {
+                result = "0" + result;
+            }
+        }
+
+        return result;
+    };
+
+    var getCanonicalTimezone = function (timezone) {
+        /// <summary>Gets the canonical timezone representation.</summary>
+        /// <param name="timezone" type="String">Timezone representation.</param>
+        /// <returns type="String">An 'Z' string if the timezone is absent or 0; the timezone otherwise.</returns>
+
+        return (!timezone || timezone === "Z" || timezone === "+00:00" || timezone === "-00:00") ? "Z" : timezone;
+    };
+
+    var getCollectionType = function (typeName) {
+        /// <summary>Gets the type of a collection type name.</summary>
+        /// <param name="typeName" type="String">Type name of the collection.</param>
+        /// <returns type="String">Type of the collection; null if the type name is not a collection type.</returns>
+
+        if (typeof typeName === "string") {
+            var end = typeName.indexOf(")", 10);
+            if (typeName.indexOf("Collection(") === 0 && end > 0) {
+                return typeName.substring(11, end);
+            }
+        }
+        return null;
+    };
+
+    var invokeRequest = function (request, success, error, handler, httpClient, context) {
+        /// <summary>Sends a request containing OData payload to a server.</summary>
+        /// <param name="request">Object that represents the request to be sent..</param>
+        /// <param name="success">Callback for a successful read operation.</param>
+        /// <param name="error">Callback for handling errors.</param>
+        /// <param name="handler">Handler for data serialization.</param>
+        /// <param name="httpClient">HTTP client layer.</param>
+        /// <param name="context">Context used for processing the request</param>
+
+        return httpClient.request(request, function (response) {
+            try {
+                if (response.headers) {
+                    normalizeHeaders(response.headers);
+                }
+
+                if (response.data === undefined && response.statusCode !== 204) {
+                    handler.read(response, context);
+                }
+            } catch (err) {
+                if (err.request === undefined) {
+                    err.request = request;
+                }
+                if (err.response === undefined) {
+                    err.response = response;
+                }
+                error(err);
+                return;
+            }
+
+            success(response.data, response);
+        }, error);
+    };
+
+    var isBatch = function (value) {
+        /// <summary>Tests whether a value is a batch object in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <returns type="Boolean">True is the value is a batch object; false otherwise.</returns>
+
+        return isComplex(value) && isArray(value.__batchRequests);
+    };
+
+    // Regular expression used for testing and parsing for a collection type.
+    var collectionTypeRE = /Collection\((.*)\)/;
+
+    var isCollection = function (value, typeName) {
+        /// <summary>Tests whether a value is a collection value in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <param name="typeName" type="Sting">Type name of the value. This is used to disambiguate from a collection property value.</param>
+        /// <returns type="Boolean">True is the value is a feed value; false otherwise.</returns>
+
+        var colData = value && value.results || value;
+        return !!colData &&
+            (isCollectionType(typeName)) ||
+            (!typeName && isArray(colData) && !isComplex(colData[0]));
+    };
+
+    var isCollectionType = function (typeName) {
+        /// <summary>Checks whether the specified type name is a collection type.</summary>
+        /// <param name="typeName" type="String">Name of type to check.</param>
+        /// <returns type="Boolean">True if the type is the name of a collection type; false otherwise.</returns>
+        return collectionTypeRE.test(typeName);
+    };
+
+    var isComplex = function (value) {
+        /// <summary>Tests whether a value is a complex type value in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <returns type="Boolean">True is the value is a complex type value; false otherwise.</returns>
+
+        return !!value &&
+            isObject(value) &&
+            !isArray(value) &&
+            !isDate(value);
+    };
+
+    var isDateTimeOffset = function (value) {
+        /// <summary>Checks whether a Date object is DateTimeOffset value</summary>
+        /// <param name="value" type="Date" mayBeNull="false">Value to check.</param>
+        /// <returns type="Boolean">true if the value is a DateTimeOffset, false otherwise.</returns>
+        return (value.__edmType === "Edm.DateTimeOffset" || (!value.__edmType && value.__offset));
+    };
+
+    var isDeferred = function (value) {
+        /// <summary>Tests whether a value is a deferred navigation property in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <returns type="Boolean">True is the value is a deferred navigation property; false otherwise.</returns>
+
+        if (!value && !isComplex(value)) {
+            return false;
+        }
+        var metadata = value.__metadata || {};
+        var deferred = value.__deferred || {};
+        return !metadata.type && !!deferred.uri;
+    };
+
+    var isEntry = function (value) {
+        /// <summary>Tests whether a value is an entry object in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <returns type="Boolean">True is the value is an entry object; false otherwise.</returns>
+
+        return isComplex(value) && value.__metadata && "uri" in value.__metadata;
+    };
+
+    var isFeed = function (value, typeName) {
+        /// <summary>Tests whether a value is a feed value in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <param name="typeName" type="Sting">Type name of the value. This is used to disambiguate from a collection property value.</param>
+        /// <returns type="Boolean">True is the value is a feed value; false otherwise.</returns>
+
+        var feedData = value && value.results || value;
+        return isArray(feedData) && (
+            (!isCollectionType(typeName)) &&
+            (isComplex(feedData[0]))
+        );
+    };
+
+    var isGeographyEdmType = function (typeName) {
+        /// <summary>Checks whether the specified type name is a geography EDM type.</summary>
+        /// <param name="typeName" type="String">Name of type to check.</param>
+        /// <returns type="Boolean">True if the type is a geography EDM type; false otherwise.</returns>
+
+        return contains(geographyEdmTypes, typeName);
+    };
+
+    var isGeometryEdmType = function (typeName) {
+        /// <summary>Checks whether the specified type name is a geometry EDM type.</summary>
+        /// <param name="typeName" type="String">Name of type to check.</param>
+        /// <returns type="Boolean">True if the type is a geometry EDM type; false otherwise.</returns>
+
+        return contains(geometryEdmTypes, typeName);
+    };
+
+    var isNamedStream = function (value) {
+        /// <summary>Tests whether a value is a named stream value in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <returns type="Boolean">True is the value is a named stream; false otherwise.</returns>
+
+        if (!value && !isComplex(value)) {
+            return false;
+        }
+        var metadata = value.__metadata;
+        var mediaResource = value.__mediaresource;
+        return !metadata && !!mediaResource && !!mediaResource.media_src;
+    };
+
+    var isPrimitive = function (value) {
+        /// <summary>Tests whether a value is a primitive type value in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <remarks>
+        ///    Date objects are considered primitive types by the library.
+        /// </remarks>
+        /// <returns type="Boolean">True is the value is a primitive type value.</returns>
+
+        return isDate(value) ||
+            typeof value === "string" ||
+            typeof value === "number" ||
+            typeof value === "boolean";
+    };
+
+    var isPrimitiveEdmType = function (typeName) {
+        /// <summary>Checks whether the specified type name is a primitive EDM type.</summary>
+        /// <param name="typeName" type="String">Name of type to check.</param>
+        /// <returns type="Boolean">True if the type is a primitive EDM type; false otherwise.</returns>
+
+        return contains(primitiveEdmTypes, typeName);
+    };
+
+    var navigationPropertyKind = function (value, propertyModel) {
+        /// <summary>Gets the kind of a navigation property value.</summary>
+        /// <param name="value">Value of the navigation property.</param>
+        /// <param name="propertyModel" type="Object" optional="true">
+        ///     Object that describes the navigation property in an OData conceptual schema.
+        /// </param>
+        /// <remarks>
+        ///     The returned string is as follows
+        /// </remarks>
+        /// <returns type="String">String value describing the kind of the navigation property; null if the kind cannot be determined.</returns>
+
+        if (isDeferred(value)) {
+            return "deferred";
+        }
+        if (isEntry(value)) {
+            return "entry";
+        }
+        if (isFeed(value)) {
+            return "feed";
+        }
+        if (propertyModel && propertyModel.relationship) {
+            if (value === null || value === undefined || !isFeed(value)) {
+                return "entry";
+            }
+            return "feed";
+        }
+        return null;
+    };
+
+    var lookupProperty = function (properties, name) {
+        /// <summary>Looks up a property by name.</summary>
+        /// <param name="properties" type="Array" mayBeNull="true">Array of property objects as per EDM metadata.</param>
+        /// <param name="name" type="String">Name to look for.</param>
+        /// <returns type="Object">The property object; null if not found.</returns>
+
+        return find(properties, function (property) {
+            return property.name === name;
+        });
+    };
+
+    var lookupInMetadata = function (name, metadata, kind) {
+        /// <summary>Looks up a type object by name.</summary>
+        /// <param name="name" type="String">Name, possibly null or empty.</param>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <param name="kind" type="String">Kind of object to look for as per EDM metadata.</param>
+        /// <returns>An type description if the name is found; null otherwise.</returns>
+
+        return (name) ? forEachSchema(metadata, function (schema) {
+            return lookupInSchema(name, schema, kind);
+        }) : null;
+    };
+
+    var lookupEntitySet = function (entitySets, name) {
+        /// <summary>Looks up a entity set by name.</summary>
+        /// <param name="properties" type="Array" mayBeNull="true">Array of entity set objects as per EDM metadata.</param>
+        /// <param name="name" type="String">Name to look for.</param>
+        /// <returns type="Object">The entity set object; null if not found.</returns>
+
+        return find(entitySets, function (entitySet) {
+            return entitySet.name === name;
+        });
+    };
+
+    var lookupComplexType = function (name, metadata) {
+        /// <summary>Looks up a complex type object by name.</summary>
+        /// <param name="name" type="String">Name, possibly null or empty.</param>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <returns>A complex type description if the name is found; null otherwise.</returns>
+
+        return lookupInMetadata(name, metadata, "complexType");
+    };
+
+    var lookupEntityType = function (name, metadata) {
+        /// <summary>Looks up an entity type object by name.</summary>
+        /// <param name="name" type="String">Name, possibly null or empty.</param>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <returns>An entity type description if the name is found; null otherwise.</returns>
+
+        return lookupInMetadata(name, metadata, "entityType");
+    };
+
+    var lookupDefaultEntityContainer = function (metadata) {
+        /// <summary>Looks up an</summary>
+        /// <param name="name" type="String">Name, possibly null or empty.</param>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <returns>An entity container description if the name is found; null otherwise.</returns>
+
+        return forEachSchema(metadata, function (schema) {
+            return find(schema.entityContainer, function (container) {
+                return parseBool(container.isDefaultEntityContainer);
+            });
+        });
+    };
+
+    var lookupEntityContainer = function (name, metadata) {
+        /// <summary>Looks up an entity container object by name.</summary>
+        /// <param name="name" type="String">Name, possibly null or empty.</param>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <returns>An entity container description if the name is found; null otherwise.</returns>
+
+        return lookupInMetadata(name, metadata, "entityContainer");
+    };
+
+    var lookupFunctionImport = function (functionImports, name) {
+        /// <summary>Looks up a function import by name.</summary>
+        /// <param name="properties" type="Array" mayBeNull="true">Array of function import objects as per EDM metadata.</param>
+        /// <param name="name" type="String">Name to look for.</param>
+        /// <returns type="Object">The entity set object; null if not found.</returns>
+
+        return find(functionImports, function (functionImport) {
+            return functionImport.name === name;
+        });
+    };
+
+    var lookupNavigationPropertyType = function (navigationProperty, metadata) {
+        /// <summary>Looks up the target entity type for a navigation property.</summary>
+        /// <param name="navigationProperty" type="Object"></param>
+        /// <param name="metadata" type="Object"></param>
+        /// <returns type="String">The entity type name for the specified property, null if not found.</returns>
+
+        var result = null;
+        if (navigationProperty) {
+            var rel = navigationProperty.relationship;
+            var association = forEachSchema(metadata, function (schema) {
+                // The name should be the namespace qualified name in 'ns'.'type' format.
+                var nameOnly = removeNamespace(schema["namespace"], rel);
+                var associations = schema.association;
+                if (nameOnly && associations) {
+                    var i, len;
+                    for (i = 0, len = associations.length; i < len; i++) {
+                        if (associations[i].name === nameOnly) {
+                            return associations[i];
+                        }
+                    }
+                }
+                return null;
+            });
+
+            if (association) {
+                var end = association.end[0];
+                if (end.role !== navigationProperty.toRole) {
+                    end = association.end[1];
+                    // For metadata to be valid, end.role === navigationProperty.toRole now.
+                }
+                result = end.type;
+            }
+        }
+        return result;
+    };
+
+    var lookupNavigationPropertyEntitySet = function (navigationProperty, sourceEntitySetName, metadata) {
+        /// <summary>Looks up the target entityset name for a navigation property.</summary>
+        /// <param name="navigationProperty" type="Object"></param>
+        /// <param name="metadata" type="Object"></param>
+        /// <returns type="String">The entityset name for the specified property, null if not found.</returns>
+
+        if (navigationProperty) {
+            var rel = navigationProperty.relationship;
+            var associationSet = forEachSchema(metadata, function (schema) {
+                var containers = schema.entityContainer;
+                for (var i = 0; i < containers.length; i++) {
+                    var associationSets = containers[i].associationSet;
+                    if (associationSets) {
+                        for (var j = 0; j < associationSets.length; j++) {
+                            if (associationSets[j].association == rel) {
+                                return associationSets[j];
+                            }
+                        }
+                    }
+                }
+                return null;
+            });
+            if (associationSet && associationSet.end[0] && associationSet.end[1]) {
+                return (associationSet.end[0].entitySet == sourceEntitySetName) ? associationSet.end[1].entitySet : associationSet.end[0].entitySet;
+            }
+        }
+        return null;
+    };
+
+    var getEntitySetInfo = function (entitySetName, metadata) {
+        /// <summary>Gets the entitySet info, container name and functionImports for an entitySet</summary>
+        /// <param name="navigationProperty" type="Object"></param>
+        /// <param name="metadata" type="Object"></param>
+        /// <returns type="Object">The info about the entitySet.</returns>
+
+        var info = forEachSchema(metadata, function (schema) {
+            var containers = schema.entityContainer;
+            for (var i = 0; i < containers.length; i++) {
+                var entitySets = containers[i].entitySet;
+                if (entitySets) {
+                    for (var j = 0; j < entitySets.length; j++) {
+                        if (entitySets[j].name == entitySetName) {
+                            return { entitySet: entitySets[j], containerName: containers[i].name, functionImport: containers[i].functionImport };
+                        }
+                    }
+                }
+            }
+            return null;
+        });
+
+        return info;
+    };
+
+    var removeNamespace = function (ns, fullName) {
+        /// <summary>Given an expected namespace prefix, removes it from a full name.</summary>
+        /// <param name="ns" type="String">Expected namespace.</param>
+        /// <param name="fullName" type="String">Full name in 'ns'.'name' form.</param>
+        /// <returns type="String">The local name, null if it isn't found in the expected namespace.</returns>
+
+        if (fullName.indexOf(ns) === 0 && fullName.charAt(ns.length) === ".") {
+            return fullName.substr(ns.length + 1);
+        }
+
+        return null;
+    };
+
+    var lookupInSchema = function (name, schema, kind) {
+        /// <summary>Looks up a schema object by name.</summary>
+        /// <param name="name" type="String">Name (assigned).</param>
+        /// <param name="schema">Schema object as per EDM metadata.</param>
+        /// <param name="kind" type="String">Kind of object to look for as per EDM metadata.</param>
+        /// <returns>An entity type description if the name is found; null otherwise.</returns>
+
+        if (name && schema) {
+            // The name should be the namespace qualified name in 'ns'.'type' format.
+            var nameOnly = removeNamespace(schema["namespace"], name);
+            if (nameOnly) {
+                return find(schema[kind], function (item) {
+                    return item.name === nameOnly;
+                });
+            }
+        }
+        return null;
+    };
+
+    var maxVersion = function (left, right) {
+        /// <summary>Compares to version strings and returns the higher one.</summary>
+        /// <param name="left" type="String">Version string in the form "major.minor.rev"</param>
+        /// <param name="right" type="String">Version string in the form "major.minor.rev"</param>
+        /// <returns type="String">The higher version string.</returns>
+
+        if (left === right) {
+            return left;
+        }
+
+        var leftParts = left.split(".");
+        var rightParts = right.split(".");
+
+        var len = (leftParts.length >= rightParts.length) ?
+            leftParts.length :
+            rightParts.length;
+
+        for (var i = 0; i < len; i++) {
+            var leftVersion = leftParts[i] && parseInt10(leftParts[i]);
+            var rightVersion = rightParts[i] && parseInt10(rightParts[i]);
+            if (leftVersion > rightVersion) {
+                return left;
+            }
+            if (leftVersion < rightVersion) {
+                return right;
+            }
+        }
+    };
+
+    var normalHeaders = {
+        "accept": "Accept",
+        "content-type": "Content-Type",
+        "dataserviceversion": "DataServiceVersion",
+        "maxdataserviceversion": "MaxDataServiceVersion"
+    };
+
+    var normalizeHeaders = function (headers) {
+        /// <summary>Normalizes headers so they can be found with consistent casing.</summary>
+        /// <param name="headers" type="Object">Dictionary of name/value pairs.</param>
+
+        for (var name in headers) {
+            var lowerName = name.toLowerCase();
+            var normalName = normalHeaders[lowerName];
+            if (normalName && name !== normalName) {
+                var val = headers[name];
+                delete headers[name];
+                headers[normalName] = val;
+            }
+        }
+    };
+
+    var parseBool = function (propertyValue) {
+        /// <summary>Parses a string into a boolean value.</summary>
+        /// <param name="propertyValue">Value to parse.</param>
+        /// <returns type="Boolean">true if the property value is 'true'; false otherwise.</returns>
+
+        if (typeof propertyValue === "boolean") {
+            return propertyValue;
+        }
+
+        return typeof propertyValue === "string" && propertyValue.toLowerCase() === "true";
+    };
+
+
+    // The captured indices for this expression are:
+    // 0     - complete input
+    // 1,2,3 - year with optional minus sign, month, day
+    // 4,5,6 - hours, minutes, seconds
+    // 7     - optional milliseconds
+    // 8     - everything else (presumably offset information)
+    var parseDateTimeRE = /^(-?\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d+))?(.*)$/;
+
+    var parseDateTimeMaybeOffset = function (value, withOffset, nullOnError) {
+        /// <summary>Parses a string into a DateTime value.</summary>
+        /// <param name="value" type="String">Value to parse.</param>
+        /// <param name="withOffset" type="Boolean">Whether offset is expected.</param>
+        /// <returns type="Date">The parsed value.</returns>
+
+        // We cannot parse this in cases of failure to match or if offset information is specified.
+        var parts = parseDateTimeRE.exec(value);
+        var offset = (parts) ? getCanonicalTimezone(parts[8]) : null;
+
+        if (!parts || (!withOffset && offset !== "Z")) {
+            if (nullOnError) {
+                return null;
+            }
+            throw { message: "Invalid date/time value" };
+        }
+
+        // Pre-parse years, account for year '0' being invalid in dateTime.
+        var year = parseInt10(parts[1]);
+        if (year <= 0) {
+            year++;
+        }
+
+        // Pre-parse optional milliseconds, fill in default. Fail if value is too precise.
+        var ms = parts[7];
+        var ns = 0;
+        if (!ms) {
+            ms = 0;
+        } else {
+            if (ms.length > 7) {
+                if (nullOnError) {
+                    return null;
+                }
+                throw { message: "Cannot parse date/time value to given precision." };
+            }
+
+            ns = formatNumberWidth(ms.substring(3), 4, true);
+            ms = formatNumberWidth(ms.substring(0, 3), 3, true);
+
+            ms = parseInt10(ms);
+            ns = parseInt10(ns);
+        }
+
+        // Pre-parse other time components and offset them if necessary.
+        var hours = parseInt10(parts[4]);
+        var minutes = parseInt10(parts[5]);
+        var seconds = parseInt10(parts[6]) || 0;
+        if (offset !== "Z") {
+            // The offset is reversed to get back the UTC date, which is
+            // what the API will eventually have.
+            var timezone = parseTimezone(offset);
+            var direction = -(timezone.d);
+            hours += timezone.h * direction;
+            minutes += timezone.m * direction;
+        }
+
+        // Set the date and time separately with setFullYear, so years 0-99 aren't biased like in Date.UTC.
+        var result = new Date();
+        result.setUTCFullYear(
+            year,                       // Year.
+            parseInt10(parts[2]) - 1,   // Month (zero-based for Date.UTC and setFullYear).
+            parseInt10(parts[3])        // Date.
+            );
+        result.setUTCHours(hours, minutes, seconds, ms);
+
+        if (isNaN(result.valueOf())) {
+            if (nullOnError) {
+                return null;
+            }
+            throw { message: "Invalid date/time value" };
+        }
+
+        if (withOffset) {
+            result.__edmType = "Edm.DateTimeOffset";
+            result.__offset = offset;
+        }
+
+        if (ns) {
+            result.__ns = ns;
+        }
+
+        return result;
+    };
+
+    var parseDateTime = function (propertyValue, nullOnError) {
+        /// <summary>Parses a string into a DateTime value.</summary>
+        /// <param name="propertyValue" type="String">Value to parse.</param>
+        /// <returns type="Date">The parsed value.</returns>
+
+        return parseDateTimeMaybeOffset(propertyValue, false, nullOnError);
+    };
+
+    var parseDateTimeOffset = function (propertyValue, nullOnError) {
+        /// <summary>Parses a string into a DateTimeOffset value.</summary>
+        /// <param name="propertyValue" type="String">Value to parse.</param>
+        /// <returns type="Date">The parsed value.</returns>
+        /// <remarks>
+        /// The resulting object is annotated with an __edmType property and
+        /// an __offset property reflecting the original intended offset of
+        /// the value. The time is adjusted for UTC time, as the current
+        /// timezone-aware Date APIs will only work with the local timezone.
+        /// </remarks>
+
+        return parseDateTimeMaybeOffset(propertyValue, true, nullOnError);
+    };
+
+    // The captured indices for this expression are:
+    // 0       - complete input
+    // 1       - direction
+    // 2,3,4   - years, months, days
+    // 5,6,7,8 - hours, minutes, seconds, miliseconds
+
+    var parseTimeRE = /^([+-])?P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)(?:\.(\d+))?S)?)?/;
+
+    var isEdmDurationValue = function(value) {
+        parseTimeRE.test(value);
+    };
+
+    var parseDuration = function (duration) {
+        /// <summary>Parses a string in xsd:duration format.</summary>
+        /// <param name="duration" type="String">Duration value.</param>
+        /// <remarks>
+        /// This method will throw an exception if the input string has a year or a month component.
+        /// </remarks>
+        /// <returns type="Object">Object representing the time</returns>
+
+        var parts = parseTimeRE.exec(duration);
+
+        if (parts === null) {
+            throw { message: "Invalid duration value." };
+        }
+
+        var years = parts[2] || "0";
+        var months = parts[3] || "0";
+        var days = parseInt10(parts[4] || 0);
+        var hours = parseInt10(parts[5] || 0);
+        var minutes = parseInt10(parts[6] || 0);
+        var seconds = parseFloat(parts[7] || 0);
+
+        if (years !== "0" || months !== "0") {
+            throw { message: "Unsupported duration value." };
+        }
+
+        var ms = parts[8];
+        var ns = 0;
+        if (!ms) {
+            ms = 0;
+        } else {
+            if (ms.length > 7) {
+                throw { message: "Cannot parse duration value to given precision." };
+            }
+
+            ns = formatNumberWidth(ms.substring(3), 4, true);
+            ms = formatNumberWidth(ms.substring(0, 3), 3, true);
+
+            ms = parseInt10(ms);
+            ns = parseInt10(ns);
+        }
+
+        ms += seconds * 1000 + minutes * 60000 + hours * 3600000 + days * 86400000;
+
+        if (parts[1] === "-") {
+            ms = -ms;
+        }
+
+        var result = { ms: ms, __edmType: "Edm.Time" };
+
+        if (ns) {
+            result.ns = ns;
+        }
+        return result;
+    };
+
+    var parseTimezone = function (timezone) {
+        /// <summary>Parses a timezone description in (+|-)nn:nn format.</summary>
+        /// <param name="timezone" type="String">Timezone offset.</param>
+        /// <returns type="Object">
+        /// An object with a (d)irection property of 1 for + and -1 for -,
+        /// offset (h)ours and offset (m)inutes.
+        /// </returns>
+
+        var direction = timezone.substring(0, 1);
+        direction = (direction === "+") ? 1 : -1;
+
+        var offsetHours = parseInt10(timezone.substring(1));
+        var offsetMinutes = parseInt10(timezone.substring(timezone.indexOf(":") + 1));
+        return { d: direction, h: offsetHours, m: offsetMinutes };
+    };
+
+    var prepareRequest = function (request, handler, context) {
+        /// <summary>Prepares a request object so that it can be sent through the network.</summary>
+        /// <param name="request">Object that represents the request to be sent.</param>
+        /// <param name="handler">Handler for data serialization</param>
+        /// <param name="context">Context used for preparing the request</param>
+
+        // Default to GET if no method has been specified.
+        if (!request.method) {
+            request.method = "GET";
+        }
+
+        if (!request.headers) {
+            request.headers = {};
+        } else {
+            normalizeHeaders(request.headers);
+        }
+
+        if (request.headers.Accept === undefined) {
+            request.headers.Accept = handler.accept;
+        }
+
+        if (assigned(request.data) && request.body === undefined) {
+            handler.write(request, context);
+        }
+
+        if (!assigned(request.headers.MaxDataServiceVersion)) {
+            request.headers.MaxDataServiceVersion = handler.maxDataServiceVersion || "1.0";
+        }
+    };
+
+    var traverseInternal = function (item, owner, callback) {
+        /// <summary>Traverses a tree of objects invoking callback for every value.</summary>
+        /// <param name="item" type="Object">Object or array to traverse.</param>
+        /// <param name="callback" type="Function">
+        /// Callback function with key and value, similar to JSON.parse reviver.
+        /// </param>
+        /// <returns type="Object">The object with traversed properties.</returns>
+        /// <remarks>Unlike the JSON reviver, this won't delete null members.</remarks>
+
+        if (item && typeof item === "object") {
+            for (var name in item) {
+                var value = item[name];
+                var result = traverseInternal(value, name, callback);
+                result = callback(name, result, owner);
+                if (result !== value) {
+                    if (value === undefined) {
+                        delete item[name];
+                    } else {
+                        item[name] = result;
+                    }
+                }
+            }
+        }
+
+        return item;
+    };
+
+    var traverse = function (item, callback) {
+        /// <summary>Traverses a tree of objects invoking callback for every value.</summary>
+        /// <param name="item" type="Object">Object or array to traverse.</param>
+        /// <param name="callback" type="Function">
+        /// Callback function with key and value, similar to JSON.parse reviver.
+        /// </param>
+        /// <returns type="Object">The traversed object.</returns>
+        /// <remarks>Unlike the JSON reviver, this won't delete null members.</remarks>
+
+        return callback("", traverseInternal(item, "", callback));
+    };
+
+    // DATAJS INTERNAL START
+    odata.dataItemTypeName = dataItemTypeName;
+    odata.EDM_BINARY = EDM_BINARY;
+    odata.EDM_BOOLEAN = EDM_BOOLEAN;
+    odata.EDM_BYTE = EDM_BYTE;
+    odata.EDM_DATETIME = EDM_DATETIME;
+    odata.EDM_DATETIMEOFFSET = EDM_DATETIMEOFFSET;
+    odata.EDM_DECIMAL = EDM_DECIMAL;
+    odata.EDM_DOUBLE = EDM_DOUBLE;
+    odata.EDM_GEOGRAPHY = EDM_GEOGRAPHY;
+    odata.EDM_GEOGRAPHY_POINT = EDM_GEOGRAPHY_POINT;
+    odata.EDM_GEOGRAPHY_LINESTRING = EDM_GEOGRAPHY_LINESTRING;
+    odata.EDM_GEOGRAPHY_POLYGON = EDM_GEOGRAPHY_POLYGON;
+    odata.EDM_GEOGRAPHY_COLLECTION = EDM_GEOGRAPHY_COLLECTION;
+    odata.EDM_GEOGRAPHY_MULTIPOLYGON = EDM_GEOGRAPHY_MULTIPOLYGON;
+    odata.EDM_GEOGRAPHY_MULTILINESTRING = EDM_GEOGRAPHY_MULTILINESTRING;
+    odata.EDM_GEOGRAPHY_MULTIPOINT = EDM_GEOGRAPHY_MULTIPOINT;
+    odata.EDM_GEOMETRY = EDM_GEOMETRY;
+    odata.EDM_GEOMETRY_POINT = EDM_GEOMETRY_POINT;
+    odata.EDM_GEOMETRY_LINESTRING = EDM_GEOMETRY_LINESTRING;
+    odata.EDM_GEOMETRY_POLYGON = EDM_GEOMETRY_POLYGON;
+    odata.EDM_GEOMETRY_COLLECTION = EDM_GEOMETRY_COLLECTION;
+    odata.EDM_GEOMETRY_MULTIPOLYGON = EDM_GEOMETRY_MULTIPOLYGON;
+    odata.EDM_GEOMETRY_MULTILINESTRING = EDM_GEOMETRY_MULTILINESTRING;
+    odata.EDM_GEOMETRY_MULTIPOINT = EDM_GEOMETRY_MULTIPOINT;
+    odata.EDM_GUID = EDM_GUID;
+    odata.EDM_INT16 = EDM_INT16;
+    odata.EDM_INT32 = EDM_INT32;
+    odata.EDM_INT64 = EDM_INT64;
+    odata.EDM_SBYTE = EDM_SBYTE;
+    odata.EDM_SINGLE = EDM_SINGLE;
+    odata.EDM_STRING = EDM_STRING;
+    odata.EDM_TIME = EDM_TIME;
+    odata.GEOJSON_POINT = GEOJSON_POINT;
+    odata.GEOJSON_LINESTRING = GEOJSON_LINESTRING;
+    odata.GEOJSON_POLYGON = GEOJSON_POLYGON;
+    odata.GEOJSON_MULTIPOINT = GEOJSON_MULTIPOINT;
+    odata.GEOJSON_MULTILINESTRING = GEOJSON_MULTILINESTRING;
+    odata.GEOJSON_MULTIPOLYGON = GEOJSON_MULTIPOLYGON;
+    odata.GEOJSON_GEOMETRYCOLLECTION = GEOJSON_GEOMETRYCOLLECTION;
+    odata.forEachSchema = forEachSchema;
+    odata.formatDateTimeOffset = formatDateTimeOffset;
+    odata.formatDuration = formatDuration;
+    odata.formatNumberWidth = formatNumberWidth;
+    odata.getCanonicalTimezone = getCanonicalTimezone;
+    odata.getCollectionType = getCollectionType;
+    odata.invokeRequest = invokeRequest;
+    odata.isBatch = isBatch;
+    odata.isCollection = isCollection;
+    odata.isCollectionType = isCollectionType;
+    odata.isComplex = isComplex;
+    odata.isDateTimeOffset = isDateTimeOffset;
+    odata.isDeferred = isDeferred;
+    odata.isEntry = isEntry;
+    odata.isFeed = isFeed;
+    odata.isGeographyEdmType = isGeographyEdmType;
+    odata.isGeometryEdmType = isGeometryEdmType;
+    odata.isNamedStream = isNamedStream;
+    odata.isPrimitive = isPrimitive;
+    odata.isPrimitiveEdmType = isPrimitiveEdmType;
+    odata.lookupComplexType = lookupComplexType;
+    odata.lookupDefaultEntityContainer = lookupDefaultEntityContainer;
+    odata.lookupEntityContainer = lookupEntityContainer;
+    odata.lookupEntitySet = lookupEntitySet;
+    odata.lookupEntityType = lookupEntityType;
+    odata.lookupFunctionImport = lookupFunctionImport;
+    odata.lookupNavigationPropertyType = lookupNavigationPropertyType;
+    odata.lookupNavigationPropertyEntitySet = lookupNavigationPropertyEntitySet;
+    odata.lookupInSchema = lookupInSchema;
+    odata.lookupProperty = lookupProperty;
+    odata.lookupInMetadata = lookupInMetadata;
+    odata.getEntitySetInfo = getEntitySetInfo;
+    odata.maxVersion = maxVersion;
+    odata.navigationPropertyKind = navigationPropertyKind;
+    odata.normalizeHeaders = normalizeHeaders;
+    odata.parseBool = parseBool;
+    odata.parseDateTime = parseDateTime;
+    odata.parseDateTimeOffset = parseDateTimeOffset;
+    odata.parseDuration = parseDuration;
+    odata.parseTimezone = parseTimezone;
+    odata.parseInt10 = parseInt10;
+    odata.prepareRequest = prepareRequest;
+    odata.removeNamespace = removeNamespace;
+    odata.traverse = traverse;
+
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/odata-xml.js b/JSLib/src/odata-xml.js
new file mode 100644
index 0000000..b2d00f6
--- /dev/null
+++ b/JSLib/src/odata-xml.js
@@ -0,0 +1,850 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-xml.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports.
+
+    var djsassert = datajs.djsassert;
+    var http = datajs.http;
+    var isArray = datajs.isArray;
+    var isDate = datajs.isDate;
+    var isObject = datajs.isObject;
+    var normalizeURI = datajs.normalizeURI;
+    var parseInt10 = datajs.parseInt10;
+    var xmlAppendChild = datajs.xmlAppendChild;
+    var xmlAppendChildren = datajs.xmlAppendChildren;
+    var xmlAttributes = datajs.xmlAttributes;
+    var xmlBaseURI = datajs.xmlBaseURI;
+    var xmlChildElements = datajs.xmlChildElements;
+    var xmlDom = datajs.xmlDom;
+    var xmlFirstChildElement = datajs.xmlFirstChildElement;
+    var xmlInnerText = datajs.xmlInnerText;
+    var xmlLocalName = datajs.xmlLocalName;
+    var xmlNamespaceURI = datajs.xmlNamespaceURI;
+    var xmlNewAttribute = datajs.xmlNewAttribute;
+    var xmlNewElement = datajs.xmlNewElement;
+    var xmlNodeValue = datajs.xmlNodeValue;
+    var xmlNS = datajs.xmlNS;
+    var xmlnsNS = datajs.xmlnsNS;
+    var xmlParse = datajs.xmlParse;
+    var xmlQualifiedName = datajs.xmlQualifiedName;
+    var xmlSerialize = datajs.xmlSerialize;
+    var xmlSiblingElement = datajs.xmlSiblingElement;
+    var w3org = datajs.w3org;
+
+    var dataItemTypeName = odata.dataItemTypeName;
+    var EDM_BINARY = odata.EDM_BINARY;
+    var EDM_BOOLEAN = odata.EDM_BOOLEAN;
+    var EDM_BYTE = odata.EDM_BYTE;
+    var EDM_DATETIME = odata.EDM_DATETIME;
+    var EDM_DATETIMEOFFSET = odata.EDM_DATETIMEOFFSET;
+    var EDM_DECIMAL = odata.EDM_DECIMAL;
+    var EDM_DOUBLE = odata.EDM_DOUBLE;
+    var EDM_GEOGRAPHY = odata.EDM_GEOGRAPHY;
+    var EDM_GEOGRAPHY_POINT = odata.EDM_GEOGRAPHY_POINT;
+    var EDM_GEOGRAPHY_LINESTRING = odata.EDM_GEOGRAPHY_LINESTRING;
+    var EDM_GEOGRAPHY_POLYGON = odata.EDM_GEOGRAPHY_POLYGON;
+    var EDM_GEOGRAPHY_COLLECTION = odata.EDM_GEOGRAPHY_COLLECTION;
+    var EDM_GEOGRAPHY_MULTIPOLYGON = odata.EDM_GEOGRAPHY_MULTIPOLYGON;
+    var EDM_GEOGRAPHY_MULTILINESTRING = odata.EDM_GEOGRAPHY_MULTILINESTRING;
+    var EDM_GEOGRAPHY_MULTIPOINT = odata.EDM_GEOGRAPHY_MULTIPOINT;
+    var EDM_GEOMETRY = odata.EDM_GEOMETRY;
+    var EDM_GEOMETRY_POINT = odata.EDM_GEOMETRY_POINT;
+    var EDM_GEOMETRY_LINESTRING = odata.EDM_GEOMETRY_LINESTRING;
+    var EDM_GEOMETRY_POLYGON = odata.EDM_GEOMETRY_POLYGON;
+    var EDM_GEOMETRY_COLLECTION = odata.EDM_GEOMETRY_COLLECTION;
+    var EDM_GEOMETRY_MULTIPOLYGON = odata.EDM_GEOMETRY_MULTIPOLYGON;
+    var EDM_GEOMETRY_MULTILINESTRING = odata.EDM_GEOMETRY_MULTILINESTRING;
+    var EDM_GEOMETRY_MULTIPOINT = odata.EDM_GEOMETRY_MULTIPOINT;
+    var EDM_GUID = odata.EDM_GUID;
+    var EDM_INT16 = odata.EDM_INT16;
+    var EDM_INT32 = odata.EDM_INT32;
+    var EDM_INT64 = odata.EDM_INT64;
+    var EDM_SBYTE = odata.EDM_SBYTE;
+    var EDM_SINGLE = odata.EDM_SINGLE;
+    var EDM_STRING = odata.EDM_STRING;
+    var EDM_TIME = odata.EDM_TIME;
+    var GEOJSON_POINT = odata.GEOJSON_POINT;
+    var GEOJSON_LINESTRING = odata.GEOJSON_LINESTRING;
+    var GEOJSON_POLYGON = odata.GEOJSON_POLYGON;
+    var GEOJSON_MULTIPOINT = odata.GEOJSON_MULTIPOINT;
+    var GEOJSON_MULTILINESTRING = odata.GEOJSON_MULTILINESTRING;
+    var GEOJSON_MULTIPOLYGON = odata.GEOJSON_MULTIPOLYGON;
+    var GEOJSON_GEOMETRYCOLLECTION = odata.GEOJSON_GEOMETRYCOLLECTION;
+    var formatDateTimeOffset = odata.formatDateTimeOffset;
+    var formatDuration = odata.formatDuration;
+    var getCollectionType = odata.getCollectionType;
+    var gmlNewODataSpatialValue = odata.gmlNewODataSpatialValue;
+    var gmlReadODataSpatialValue = odata.gmlReadODataSpatialValue;
+    var gmlXmlNs = odata.gmlXmlNs;
+    var handler = odata.handler;
+    var isCollection = odata.isCollection;
+    var isCollectionType = odata.isCollectionType;
+    var isDeferred = odata.isDeferred;
+    var isNamedStream = odata.isNamedStream;
+    var isGeographyEdmType = odata.isGeographyEdmType;
+    var isGeometryEdmType = odata.isGeometryEdmType;
+    var isPrimitive = odata.isPrimitive;
+    var isPrimitiveEdmType = odata.isPrimitiveEdmType;
+    var lookupComplexType = odata.lookupComplexType;
+    var lookupProperty = odata.lookupProperty;
+    var maxVersion = odata.maxVersion;
+    var navigationPropertyKind = odata.navigationPropertyKind;
+    var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
+    var parseBool = odata.parseBool;
+    var parseDateTime = odata.parseDateTime;
+    var parseDateTimeOffset = odata.parseDateTimeOffset;
+    var parseDuration = odata.parseDuration;
+    var parseTimezone = odata.parseTimezone;
+
+    // CONTENT START
+
+    var xmlMediaType = "application/xml";
+
+    var ado = http + "schemas.microsoft.com/ado/";      // http://schemas.microsoft.com/ado/
+    var adoDs = ado + "2007/08/dataservices";           // http://schemas.microsoft.com/ado/2007/08/dataservices
+
+    var edmxNs = ado + "2007/06/edmx";                  // http://schemas.microsoft.com/ado/2007/06/edmx
+    var edmNs1 = ado + "2006/04/edm";                   // http://schemas.microsoft.com/ado/2006/04/edm
+    var edmNs1_1 = ado + "2007/05/edm";                 // http://schemas.microsoft.com/ado/2007/05/edm
+    var edmNs1_2 = ado + "2008/01/edm";                 // http://schemas.microsoft.com/ado/2008/01/edm
+
+    // There are two valid namespaces for Edm 2.0
+    var edmNs2a = ado + "2008/09/edm";                  // http://schemas.microsoft.com/ado/2008/09/edm
+    var edmNs2b = ado + "2009/08/edm";                  // http://schemas.microsoft.com/ado/2009/08/edm
+
+    var edmNs3 = ado + "2009/11/edm";                   // http://schemas.microsoft.com/ado/2009/11/edm
+
+    var odataXmlNs = adoDs;                             // http://schemas.microsoft.com/ado/2007/08/dataservices
+    var odataMetaXmlNs = adoDs + "/metadata";           // http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
+    var odataRelatedPrefix = adoDs + "/related/";       // http://schemas.microsoft.com/ado/2007/08/dataservices/related
+    var odataScheme = adoDs + "/scheme";                // http://schemas.microsoft.com/ado/2007/08/dataservices/scheme
+
+    var odataPrefix = "d";
+    var odataMetaPrefix = "m";
+
+    var createAttributeExtension = function (domNode, useNamespaceURI) {
+        /// <summary>Creates an extension object for the specified attribute.</summary>
+        /// <param name="domNode">DOM node for the attribute.</param>
+        /// <param name="useNamespaceURI" type="Boolean">Flag indicating if the namespaceURI property should be added to the extension object instead of the namespace property.</param>
+        /// <remarks>
+        ///    The useNamespaceURI flag is used to prevent a breaking change from older versions of datajs in which extension
+        ///    objects created for Atom extension attributes have the namespaceURI property instead of the namespace one.
+        ///    
+        ///    This flag and the namespaceURI property should be deprecated in future major versions of the library.
+        /// </remarks>
+        /// <returns type="Object">The new extension object.</returns>
+
+        djsassert(domNode.nodeType === 2, "createAttributeExtension - domNode is not an attribute node!!");
+        var extension = { name: xmlLocalName(domNode), value: domNode.value };
+        extension[useNamespaceURI ? "namespaceURI" : "namespace"] = xmlNamespaceURI(domNode);
+
+        return extension;
+    };
+
+    var createElementExtension = function (domNode, useNamespaceURI) {
+        /// <summary>Creates an extension object for the specified element.</summary>
+        /// <param name="domNode">DOM node for the element.</param>
+        /// <param name="useNamespaceURI" type="Boolean">Flag indicating if the namespaceURI property should be added to the extension object instead of the namespace property.</param>
+        /// <remarks>
+        ///    The useNamespaceURI flag is used to prevent a breaking change from older versions of datajs in which extension
+        ///    objects created for Atom extension attributes have the namespaceURI property instead of the namespace one.
+        ///    
+        ///    This flag and the namespaceURI property should be deprecated in future major versions of the library.
+        /// </remarks>
+        /// <returns type="Object">The new extension object.</returns>
+
+        djsassert(domNode.nodeType === 1, "createAttributeExtension - domNode is not an element node!!");
+
+        var attributeExtensions = [];
+        var childrenExtensions = [];
+
+        var i, len;
+        var attributes = domNode.attributes;
+        for (i = 0, len = attributes.length; i < len; i++) {
+            var attr = attributes[i];
+            if (xmlNamespaceURI(attr) !== xmlnsNS) {
+                attributeExtensions.push(createAttributeExtension(attr, useNamespaceURI));
+            }
+        }
+
+        var child = domNode.firstChild;
+        while (child != null) {
+            if (child.nodeType === 1) {
+                childrenExtensions.push(createElementExtension(child, useNamespaceURI));
+            }
+            child = child.nextSibling;
+        }
+
+        var extension = {
+            name: xmlLocalName(domNode),
+            value: xmlInnerText(domNode),
+            attributes: attributeExtensions,
+            children: childrenExtensions
+        };
+
+        extension[useNamespaceURI ? "namespaceURI" : "namespace"] = xmlNamespaceURI(domNode);
+        return extension;
+    };
+
+    var isCollectionItemElement = function (domElement) {
+        /// <summary>Checks whether the domElement is a collection item.</summary>
+        /// <param name="domElement">DOM element possibliy represnting a collection item.</param>
+        /// <returns type="Boolean">True if the domeElement belongs to the OData metadata namespace and its local name is "element"; false otherwise.</returns>
+
+        return xmlNamespaceURI(domElement) === odataXmlNs && xmlLocalName(domElement) === "element";
+    };
+
+    var makePropertyMetadata = function (type, extensions) {
+        /// <summary>Creates an object containing property metadata.</summary>
+        /// <param type="String" name="type">Property type name.</param>
+        /// <param type="Array" name="extensions">Array of attribute extension objects.</param>
+        /// <returns type="Object">Property metadata object cotaining type and extensions fields.</returns>
+
+        return { type: type, extensions: extensions };
+    };
+
+    var odataInferTypeFromPropertyXmlDom = function (domElement) {
+        /// <summary>Infers type of a property based on its xml DOM tree.</summary>
+        /// <param name="domElement">DOM element for the property.</param>
+        /// <returns type="String">Inferred type name; null if the type cannot be determined.</returns>
+
+        if (xmlFirstChildElement(domElement, gmlXmlNs)) {
+            return EDM_GEOMETRY;
+        }
+
+        var firstChild = xmlFirstChildElement(domElement, odataXmlNs);
+        if (!firstChild) {
+            return EDM_STRING;
+        }
+
+        if (isCollectionItemElement(firstChild)) {
+            var sibling = xmlSiblingElement(firstChild, odataXmlNs);
+            if (sibling && isCollectionItemElement(sibling)) {
+                // More than one <element> tag have been found, it can be safely assumed that this is a collection property.
+                return "Collection()";
+            }
+        }
+
+        return null;
+    };
+
+    var xmlReadODataPropertyAttributes = function (domElement) {
+        /// <summary>Reads the attributes of a property DOM element in an OData XML document.</summary>
+        /// <param name="domElement">DOM element for the property.</param>
+        /// <returns type="Object">Object containing the property type, if it is null, and its attribute extensions.</returns>
+
+        var type = null;
+        var isNull = false;
+        var extensions = [];
+
+        xmlAttributes(domElement, function (attribute) {
+            var nsURI = xmlNamespaceURI(attribute);
+            var localName = xmlLocalName(attribute);
+            var value = xmlNodeValue(attribute);
+
+            if (nsURI === odataMetaXmlNs) {
+                if (localName === "null") {
+                    isNull = (value.toLowerCase() === "true");
+                    return;
+                }
+
+                if (localName === "type") {
+                    type = value;
+                    return;
+                }
+            }
+
+            if (nsURI !== xmlNS && nsURI !== xmlnsNS) {
+                extensions.push(createAttributeExtension(attribute, true));
+                return;
+            }
+        });
+
+        return { type: (!type && isNull ? EDM_STRING : type), isNull: isNull, extensions: extensions };
+    };
+
+    var xmlReadODataProperty = function (domElement) {
+        /// <summary>Reads a property DOM element in an OData XML document.</summary>
+        /// <param name="domElement">DOM element for the property.</param>
+        /// <returns type="Object">Object with name, value, and metadata for the property.</returns>
+
+        if (xmlNamespaceURI(domElement) !== odataXmlNs) {
+            // domElement is not a proprety element because it is not in the odata xml namespace.
+            return null;
+        }
+
+        var propertyName = xmlLocalName(domElement);
+        var propertyAttributes = xmlReadODataPropertyAttributes(domElement);
+
+        var propertyIsNull = propertyAttributes.isNull;
+        var propertyType = propertyAttributes.type;
+
+        var propertyMetadata = makePropertyMetadata(propertyType, propertyAttributes.extensions);
+        var propertyValue = propertyIsNull ? null : xmlReadODataPropertyValue(domElement, propertyType, propertyMetadata);
+
+        return { name: propertyName, value: propertyValue, metadata: propertyMetadata };
+    };
+
+    var xmlReadODataPropertyValue = function (domElement, propertyType, propertyMetadata) {
+        /// <summary>Reads the value of a property in an OData XML document.</summary>
+        /// <param name="domElement">DOM element for the property.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <param name="propertyMetadata" type="Object">Object that will store metadata about the property.</param>
+        /// <returns>Property value.</returns>
+
+        if (!propertyType) {
+            propertyType = odataInferTypeFromPropertyXmlDom(domElement);
+            propertyMetadata.type = propertyType;
+        }
+
+        var isGeograhpyType = isGeographyEdmType(propertyType);
+        if (isGeograhpyType || isGeometryEdmType(propertyType)) {
+            return xmlReadODataSpatialPropertyValue(domElement, propertyType, isGeograhpyType);
+        }
+
+        if (isPrimitiveEdmType(propertyType)) {
+            return xmlReadODataEdmPropertyValue(domElement, propertyType);
+        }
+
+        if (isCollectionType(propertyType)) {
+            return xmlReadODataCollectionPropertyValue(domElement, propertyType, propertyMetadata);
+        }
+
+        return xmlReadODataComplexPropertyValue(domElement, propertyType, propertyMetadata);
+    };
+
+    var xmlReadODataSpatialPropertyValue = function (domElement, propertyType, isGeography) {
+        /// <summary>Reads the value of an spatial property in an OData XML document.</summary>
+        /// <param name="property">DOM element for the spatial property.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>Spatial property value in GeoJSON format.</returns>
+
+        var gmlRoot = xmlFirstChildElement(domElement, gmlXmlNs);
+        djsassert(gmlRoot, "xmlReadODataSpatialPropertyValue - domElement doesn't have a child element that belongs to the gml namespace!!");
+
+        var value = gmlReadODataSpatialValue(gmlRoot, isGeography);
+        value.__metadata = { type: propertyType };
+        return value;
+    };
+
+    var xmlReadODataEdmPropertyValue = function (domNode, propertyType) {
+        /// <summary>Reads the value of an EDM property in an OData XML document.</summary>
+        /// <param name="donNode">DOM node for the EDM property.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <returns>EDM property value.</returns>
+
+        var propertyValue = xmlNodeValue(domNode) || "";
+
+        switch (propertyType) {
+            case EDM_BOOLEAN:
+                return parseBool(propertyValue);
+            case EDM_BINARY:
+            case EDM_DECIMAL:
+            case EDM_GUID:
+            case EDM_INT64:
+            case EDM_STRING:
+                return propertyValue;
+            case EDM_BYTE:
+            case EDM_INT16:
+            case EDM_INT32:
+            case EDM_SBYTE:
+                return parseInt10(propertyValue);
+            case EDM_DOUBLE:
+            case EDM_SINGLE:
+                return parseFloat(propertyValue);
+            case EDM_TIME:
+                return parseDuration(propertyValue);
+            case EDM_DATETIME:
+                return parseDateTime(propertyValue);
+            case EDM_DATETIMEOFFSET:
+                return parseDateTimeOffset(propertyValue);
+        }
+
+        return propertyValue;
+    };
+
+    var xmlReadODataComplexPropertyValue = function(domElement, propertyType, propertyMetadata) {
+        /// <summary>Reads the value of a complex type property in an OData XML document.</summary>
+        /// <param name="property">DOM element for the complex type property.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <param name="propertyMetadata" type="Object">Object that will store metadata about the property.</param>
+        /// <returns type="Object">Complex type property value.</returns>
+
+        var propertyValue = { __metadata: { type: propertyType } };
+        xmlChildElements(domElement, function(child) {
+            var childProperty = xmlReadODataProperty(child);
+            var childPropertyName = childProperty.name;
+
+            propertyMetadata.properties = propertyMetadata.properties || {};
+            propertyMetadata.properties[childPropertyName] = childProperty.metadata;
+            propertyValue[childPropertyName] = childProperty.value;
+        });
+
+        return propertyValue;
+    };
+
+    var xmlReadODataCollectionPropertyValue = function (domElement, propertyType, propertyMetadata) {
+        /// <summary>Reads the value of a collection property in an OData XML document.</summary>
+        /// <param name="property">DOM element for the collection property.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <param name="propertyMetadata" type="Object">Object that will store metadata about the property.</param>
+        /// <returns type="Object">Collection property value.</returns>
+
+        var items = [];
+        var itemsMetadata = propertyMetadata.elements = [];
+        var collectionType = getCollectionType(propertyType);
+
+        xmlChildElements(domElement, function (child) {
+            if (isCollectionItemElement(child)) {
+                var itemAttributes = xmlReadODataPropertyAttributes(child);
+                var itemExtensions = itemAttributes.extensions;
+                var itemType = itemAttributes.type || collectionType;
+                var itemMetadata = makePropertyMetadata(itemType, itemExtensions);
+
+                var item = xmlReadODataPropertyValue(child, itemType, itemMetadata);
+
+                items.push(item);
+                itemsMetadata.push(itemMetadata);
+            }
+        });
+
+        return { __metadata: { type: propertyType === "Collection()" ? null : propertyType }, results: items };
+    };
+
+    var readODataXmlDocument = function (xmlRoot, baseURI) {
+        /// <summary>Reads an OData link(s) producing an object model in return.</summary>
+        /// <param name="xmlRoot">Top-level element to read.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the XML payload.</param>
+        /// <returns type="Object">The object model representing the specified element.</returns>
+
+        if (xmlNamespaceURI(xmlRoot) === odataXmlNs) {
+            baseURI = xmlBaseURI(xmlRoot, baseURI);
+            var localName = xmlLocalName(xmlRoot);
+
+            if (localName === "links") {
+                return readLinks(xmlRoot, baseURI);
+            }
+            if (localName === "uri") {
+                return readUri(xmlRoot, baseURI);
+            }
+        }
+        return undefined;
+    };
+
+    var readLinks = function (linksElement, baseURI) {
+        /// <summary>Deserializes an OData XML links element.</summary>
+        /// <param name="linksElement">XML links element.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the XML payload.</param>
+        /// <returns type="Object">A new object representing the links collection.</returns>
+
+        var uris = [];
+
+        xmlChildElements(linksElement, function (child) {
+            if (xmlLocalName(child) === "uri" && xmlNamespaceURI(child) === odataXmlNs) {
+                uris.push(readUri(child, baseURI));
+            }
+        });
+
+        return { results: uris };
+    };
+
+    var readUri = function (uriElement, baseURI) {
+        /// <summary>Deserializes an OData XML uri element.</summary>
+        /// <param name="uriElement">XML uri element.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the XML payload.</param>
+        /// <returns type="Object">A new object representing the uri.</returns>
+
+        var uri = xmlInnerText(uriElement) || "";
+        return { uri: normalizeURI(uri, baseURI) };
+    };
+
+    var xmlODataInferSpatialValueGeoJsonType = function (value, edmType) {
+        /// <summary>Infers the GeoJSON type from the spatial property value and the edm type name.</summary>
+        /// <param name="value" type="Object">Spatial property value in GeoJSON format.</param>
+        /// <param name="edmType" type="String" mayBeNull="true" optional="true">Spatial property edm type.<param>
+        /// <remarks>
+        ///   If the edmType parameter is null, undefined, "Edm.Geometry" or "Edm.Geography", then the function returns
+        ///   the GeoJSON type indicated by the value's type property.
+        ///
+        ///   If the edmType parameter is specified or is not one of the base spatial types, then it is used to
+        ///   determine the GeoJSON type and the value's type property is ignored.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the spatial value. </returns>
+
+        if (edmType === EDM_GEOMETRY || edmType === EDM_GEOGRAPHY) {
+            return value && value.type;
+        }
+
+        if (edmType === EDM_GEOMETRY_POINT || edmType === EDM_GEOGRAPHY_POINT) {
+            return GEOJSON_POINT;
+        }
+
+        if (edmType === EDM_GEOMETRY_LINESTRING || edmType === EDM_GEOGRAPHY_LINESTRING) {
+            return GEOJSON_LINESTRING;
+        }
+
+        if (edmType === EDM_GEOMETRY_POLYGON || edmType === EDM_GEOGRAPHY_POLYGON) {
+            return GEOJSON_POLYGON;
+        }
+
+        if (edmType === EDM_GEOMETRY_COLLECTION || edmType === EDM_GEOGRAPHY_COLLECTION) {
+            return GEOJSON_GEOMETRYCOLLECTION;
+        }
+
+        if (edmType === EDM_GEOMETRY_MULTIPOLYGON || edmType === EDM_GEOGRAPHY_MULTIPOLYGON) {
+            return GEOJSON_MULTIPOLYGON;
+        }
+
+        if (edmType === EDM_GEOMETRY_MULTILINESTRING || edmType === EDM_GEOGRAPHY_MULTILINESTRING) {
+            return GEOJSON_MULTILINESTRING;
+        }
+
+        if (edmType === EDM_GEOMETRY_MULTIPOINT || edmType === EDM_GEOGRAPHY_MULTIPOINT) {
+            return GEOJSON_MULTIPOINT;
+        }
+
+        djsassert(false, "gmlInferGeoJsonType - edm type <" + edmType + "> was unexpected!!");
+        return null;
+    };
+
+    var xmlNewODataMetaElement = function (dom, name, children) {
+        /// <summary>Creates a new DOM element in the OData metadata namespace.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Local name of the OData metadata element to create.</param>
+        /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
+        /// <returns>New DOM element in the OData metadata namespace.</returns>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended as a child of the new DOM Element.
+        /// </remarks>
+
+        return xmlNewElement(dom, odataMetaXmlNs, xmlQualifiedName(odataMetaPrefix, name), children);
+    };
+
+    var xmlNewODataMetaAttribute = function (dom, name, value) {
+        /// <summary>Creates a new DOM attribute in the odata namespace.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Local name of the OData attribute to create.</param>
+        /// <param name="value">Attribute value.</param>
+        /// <returns>New DOM attribute in the odata namespace.</returns>
+
+        return xmlNewAttribute(dom, odataMetaXmlNs, xmlQualifiedName(odataMetaPrefix, name), value);
+    };
+
+    var xmlNewODataElement = function (dom, name, children) {
+        /// <summary>Creates a new DOM element in the OData namespace.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Local name of the OData element to create.</param>
+        /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
+        /// <returns>New DOM element in the OData namespace.</returns>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended as a child of the new DOM Element.
+        /// </remarks>
+
+        return xmlNewElement(dom, odataXmlNs, xmlQualifiedName(odataPrefix, name), children);
+    };
+
+    var xmlNewODataPrimitiveValue = function (value, typeName) {
+        /// <summary>Returns the string representation of primitive value for an OData XML document.</summary>
+        /// <param name="value">Primivite value to format.</param>
+        /// <param name="typeName" type="String" optional="true">Type name of the primitive value.</param>
+        /// <returns type="String">Formatted primitive value.</returns>
+
+        if (typeName === EDM_DATETIME || typeName === EDM_DATETIMEOFFSET || isDate(value)) {
+            return formatDateTimeOffset(value);
+        }
+        if (typeName === EDM_TIME) {
+            return formatDuration(value);
+        }
+        return value.toString();
+    };
+
+    var xmlNewODataElementInfo = function (domElement, dataServiceVersion) {
+        /// <summary>Creates an object that represents a new DOM element for an OData XML document and the data service version it requires.</summary>
+        /// <param name="domElement">New DOM element for an OData XML document.</param>
+        /// <param name="dataServiceVersion" type="String">Required data service version by the new DOM element.</param>
+        /// <returns type="Object">Object containing new DOM element and its required data service version.</returns>
+
+        return { element: domElement, dsv: dataServiceVersion };
+    };
+
+    var xmlNewODataProperty = function (dom, name, typeName, children) {
+        /// <summary>Creates a new DOM element for an entry property in an OData XML document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="typeName" type="String" optional="true">Property type name.</param>
+        /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended as a child of the new DOM Element.
+        /// </remarks>
+        /// <returns>New DOM element in the OData namespace for the entry property.</returns>
+
+        var typeAttribute = typeName ? xmlNewODataMetaAttribute(dom, "type", typeName) : null;
+        var property = xmlNewODataElement(dom, name, typeAttribute);
+        return xmlAppendChildren(property, children);
+    };
+
+    var xmlNewODataEdmProperty = function (dom, name, value, typeName) {
+        /// <summary>Creates a new DOM element for an EDM property in an OData XML document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="value">Property value.</param>
+        /// <param name="typeName" type="String" optional="true">Property type name.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the OData namespace for the EDM property and the
+        ///     required data service version for this property.
+        /// </returns>
+
+        var propertyValue = xmlNewODataPrimitiveValue(value, typeName);
+        var property = xmlNewODataProperty(dom, name, typeName, propertyValue);
+        return xmlNewODataElementInfo(property, /*dataServiceVersion*/"1.0");
+    };
+
+    var xmlNewODataNullProperty = function (dom, name, typeName, model) {
+        /// <summary>Creates a new DOM element for a null property in an OData XML document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="typeName" type="String" optional="true">Property type name.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <remarks>
+        ///     If no typeName is specified, then it will be assumed that this is a primitive type property.
+        /// </remarks>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the OData namespace for the null property and the 
+        ///     required data service version for this property.
+        /// </returns>
+
+        var nullAttribute = xmlNewODataMetaAttribute(dom, "null", "true");
+        var property = xmlNewODataProperty(dom, name, typeName, nullAttribute);
+        var dataServiceVersion = lookupComplexType(typeName, model) ? "2.0" : "1.0";
+
+        return xmlNewODataElementInfo(property, dataServiceVersion);
+    };
+
+    var xmlNewODataCollectionProperty = function (dom, name, value, typeName, collectionMetadata, collectionModel, model) {
+        /// <summary>Creates a new DOM element for a collection property in an OData XML document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="value">Property value either as an array or an object representing a collection in the library's internal representation.</param>
+        /// <param name="typeName" type="String" optional="true">Property type name.</param>
+        /// <param name="collectionMetadata" type="Object" optional="true">Object containing metadata about the collection property.</param>
+        /// <param name="collectionModel" type="Object" optional="true">Object describing the collection property in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the OData namespace for the collection property and the
+        ///     required data service version for this property.
+        /// </returns>
+
+        var itemTypeName = getCollectionType(typeName);
+        var items = isArray(value) ? value : value.results;
+        var itemMetadata = typeName ? { type: itemTypeName} : {};
+        itemMetadata.properties = collectionMetadata.properties;
+
+        var xmlProperty = xmlNewODataProperty(dom, name, itemTypeName ? typeName : null);
+
+        var i, len;
+        for (i = 0, len = items.length; i < len; i++) {
+            var itemValue = items[i];
+            var item = xmlNewODataDataElement(dom, "element", itemValue, itemMetadata, collectionModel, model);
+
+            xmlAppendChild(xmlProperty, item.element);
+        }
+        return xmlNewODataElementInfo(xmlProperty, /*dataServiceVersion*/"3.0");
+    };
+
+    var xmlNewODataComplexProperty = function (dom, name, value, typeName, propertyMetadata, propertyModel, model) {
+        /// <summary>Creates a new DOM element for a complex type property in an OData XML document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="value">Property value as an object in the library's internal representation.</param>
+        /// <param name="typeName" type="String" optional="true">Property type name.</param>
+        /// <param name="propertyMetadata" type="Object" optional="true">Object containing metadata about the complex type property.</param>
+        /// <param name="propertyModel" type="Object" optional="true">Object describing the complex type property in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the OData namespace for the complex type property and the
+        ///     required data service version for this property.
+        /// </returns>
+
+        var xmlProperty = xmlNewODataProperty(dom, name, typeName);
+        var complexTypePropertiesMetadata = propertyMetadata.properties || {};
+        var complexTypeModel = lookupComplexType(typeName, model) || {};
+
+        var dataServiceVersion = "1.0";
+
+        for (var key in value) {
+            if (key !== "__metadata") {
+                var memberValue = value[key];
+                var memberModel = lookupProperty(complexTypeModel.property, key);
+                var memberMetadata = complexTypePropertiesMetadata[key] || {};
+                var member = xmlNewODataDataElement(dom, key, memberValue, memberMetadata, memberModel, model);
+
+                dataServiceVersion = maxVersion(dataServiceVersion, member.dsv);
+                xmlAppendChild(xmlProperty, member.element);
+            }
+        }
+        return xmlNewODataElementInfo(xmlProperty, dataServiceVersion);
+    };
+
+    var xmlNewODataSpatialProperty = function (dom, name, value, typeName, isGeography) {
+        /// <summary>Creates a new DOM element for an EDM spatial property in an OData XML document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="value" type="Object">GeoJSON object containing the property value.</param>
+        /// <param name="typeName" type="String" optional="true">Property type name.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the OData namespace for the EDM property and the
+        ///     required data service version for this property.
+        /// </returns>
+
+        var geoJsonType = xmlODataInferSpatialValueGeoJsonType(value, typeName);
+
+        var gmlRoot = gmlNewODataSpatialValue(dom, value, geoJsonType, isGeography);
+        var xmlProperty = xmlNewODataProperty(dom, name, typeName, gmlRoot);
+
+        return xmlNewODataElementInfo(xmlProperty, "3.0");
+    };
+
+    var xmlNewODataDataElement = function (dom, name, value, dataItemMetadata, dataItemModel, model) {
+        /// <summary>Creates a new DOM element for a data item in an entry, complex property, or collection property.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Data item name.</param>
+        /// <param name="value" optional="true" mayBeNull="true">Value of the data item, if any.</param>
+        /// <param name="dataItemMetadata" type="Object" optional="true">Object containing metadata about the data item.</param>
+        /// <param name="dataItemModel" type="Object" optional="true">Object describing the data item in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the appropriate namespace for the data item and the
+        ///     required data service version for it.
+        /// </returns>
+
+        var typeName = dataItemTypeName(value, dataItemMetadata, dataItemModel);
+        if (isPrimitive(value)) {
+            return xmlNewODataEdmProperty(dom, name, value, typeName || EDM_STRING);
+        }
+
+        var isGeography = isGeographyEdmType(typeName);
+        if (isGeography || isGeometryEdmType(typeName)) {
+            return xmlNewODataSpatialProperty(dom, name, value, typeName, isGeography);
+        }
+
+        if (isCollection(value, typeName)) {
+            return xmlNewODataCollectionProperty(dom, name, value, typeName, dataItemMetadata, dataItemModel, model);
+        }
+
+        if (isNamedStream(value)) {
+            return null;
+        }
+
+        // This may be a navigation property.
+        var navPropKind = navigationPropertyKind(value, dataItemModel);
+        if (navPropKind !== null) {
+            return null;
+        }
+
+        if (value === null) {
+            return xmlNewODataNullProperty(dom, name, typeName);
+        }
+
+        djsassert(isObject(value), "xmlNewODataEntryProperty - property '" + name + "' is not an object");
+        return xmlNewODataComplexProperty(dom, name, value, typeName, dataItemMetadata, dataItemModel, model);
+    };
+
+    var odataNewLinkDocument = function (data) {
+        /// <summary>Writes the specified data into an OData XML document.</summary>
+        /// <param name="data">Data to write.</param>
+        /// <returns>The root of the DOM tree built.</returns>
+
+        if (data && isObject(data)) {
+            var dom = xmlDom();
+            return xmlAppendChild(dom, xmlNewODataElement(dom, "uri", data.uri));
+        }
+        // Allow for undefined to be returned.
+    };
+
+    var xmlParser = function (handler, text) {
+        /// <summary>Parses an OData XML document.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="text" type="String">Document text.</param>
+        /// <returns>An object representation of the document; undefined if not applicable.</returns>
+
+        if (text) {
+            var doc = xmlParse(text);
+            var root = xmlFirstChildElement(doc);
+            if (root) {
+                return readODataXmlDocument(root);
+            }
+        }
+
+        // Allow for undefined to be returned.
+    };
+
+    var xmlSerializer = function (handler, data, context) {
+        /// <summary>Serializes an OData XML object into a document.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="data" type="Object">Representation of feed or entry.</param>
+        /// <param name="context" type="Object">Object with parsing context.</param>
+        /// <returns>A text representation of the data object; undefined if not applicable.</returns>
+
+        var cType = context.contentType = context.contentType || contentType(xmlMediaType);
+        if (cType && cType.mediaType === xmlMediaType) {
+            return xmlSerialize(odataNewLinkDocument(data));
+        }
+        return undefined;
+    };
+
+    odata.xmlHandler = handler(xmlParser, xmlSerializer, xmlMediaType, MAX_DATA_SERVICE_VERSION);
+
+    // DATAJS INTERNAL START
+    odata.adoDs = adoDs;
+    odata.createAttributeExtension = createAttributeExtension;
+    odata.createElementExtension = createElementExtension;
+    odata.edmxNs = edmxNs;
+    odata.edmNs1 = edmNs1;
+    odata.edmNs1_1 = edmNs1_1;
+    odata.edmNs1_2 = edmNs1_2
+    odata.edmNs2a = edmNs2a;
+    odata.edmNs2b = edmNs2b;
+    odata.edmNs3 = edmNs3;
+    odata.odataMetaXmlNs = odataMetaXmlNs;
+    odata.odataMetaPrefix = odataMetaPrefix;
+    odata.odataXmlNs = odataXmlNs;
+    odata.odataPrefix = odataPrefix;
+    odata.odataScheme = odataScheme;
+    odata.odataRelatedPrefix = odataRelatedPrefix;
+    odata.xmlNewODataElement = xmlNewODataElement;
+    odata.xmlNewODataElementInfo = xmlNewODataElementInfo;
+    odata.xmlNewODataMetaAttribute = xmlNewODataMetaAttribute;
+    odata.xmlNewODataMetaElement = xmlNewODataMetaElement;
+    odata.xmlNewODataDataElement = xmlNewODataDataElement;
+    odata.xmlReadODataEdmPropertyValue = xmlReadODataEdmPropertyValue;
+    odata.xmlReadODataProperty = xmlReadODataProperty;
+
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/odata.js b/JSLib/src/odata.js
new file mode 100644
index 0000000..98b80e2
--- /dev/null
+++ b/JSLib/src/odata.js
@@ -0,0 +1,158 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports
+
+    var assigned = datajs.assigned;
+    var defined = datajs.defined;
+    var throwErrorCallback = datajs.throwErrorCallback;
+
+    var invokeRequest = odata.invokeRequest;
+    var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
+    var prepareRequest = odata.prepareRequest;
+    var metadataParser = odata.metadataParser;
+
+    // CONTENT START
+
+    var handlers = [odata.jsonHandler, odata.atomHandler, odata.xmlHandler, odata.textHandler];
+
+    var dispatchHandler = function (handlerMethod, requestOrResponse, context) {
+        /// <summary>Dispatches an operation to handlers.</summary>
+        /// <param name="handlerMethod" type="String">Name of handler method to invoke.</param>
+        /// <param name="requestOrResponse" type="Object">request/response argument for delegated call.</param>
+        /// <param name="context" type="Object">context argument for delegated call.</param>
+
+        var i, len;
+        for (i = 0, len = handlers.length; i < len && !handlers[i][handlerMethod](requestOrResponse, context); i++) {
+        }
+
+        if (i === len) {
+            throw { message: "no handler for data" };
+        }
+    };
+
+    odata.defaultSuccess = function (data) {
+        /// <summary>Default success handler for OData.</summary>
+        /// <param name="data">Data to process.</param>
+
+        window.alert(window.JSON.stringify(data));
+    };
+
+    odata.defaultError = throwErrorCallback;
+
+    odata.defaultHandler = {
+        read: function (response, context) {
+            /// <summary>Reads the body of the specified response by delegating to JSON and ATOM handlers.</summary>
+            /// <param name="response">Response object.</param>
+            /// <param name="context">Operation context.</param>
+
+            if (response && assigned(response.body) && response.headers["Content-Type"]) {
+                dispatchHandler("read", response, context);
+            }
+        },
+
+        write: function (request, context) {
+            /// <summary>Write the body of the specified request by delegating to JSON and ATOM handlers.</summary>
+            /// <param name="request">Reques tobject.</param>
+            /// <param name="context">Operation context.</param>
+
+            dispatchHandler("write", request, context);
+        },
+
+        maxDataServiceVersion: MAX_DATA_SERVICE_VERSION,
+        accept: "application/atomsvc+xml;q=0.8, application/json;odata=fullmetadata;q=0.7, application/json;q=0.5, */*;q=0.1"
+    };
+
+    odata.defaultMetadata = [];
+
+    odata.read = function (urlOrRequest, success, error, handler, httpClient, metadata) {
+        /// <summary>Reads data from the specified URL.</summary>
+        /// <param name="urlOrRequest">URL to read data from.</param>
+        /// <param name="success" type="Function" optional="true">Callback for a successful read operation.</param>
+        /// <param name="error" type="Function" optional="true">Callback for handling errors.</param>
+        /// <param name="handler" type="Object" optional="true">Handler for data serialization.</param>
+        /// <param name="httpClient" type="Object" optional="true">HTTP client layer.</param>
+        /// <param name="metadata" type="Object" optional="true">Conceptual metadata for this request.</param>
+
+        var request;
+        if (urlOrRequest instanceof String || typeof urlOrRequest === "string") {
+            request = { requestUri: urlOrRequest };
+        } else {
+            request = urlOrRequest;
+        }
+
+        return odata.request(request, success, error, handler, httpClient, metadata);
+    };
+
+    odata.request = function (request, success, error, handler, httpClient, metadata) {
+        /// <summary>Sends a request containing OData payload to a server.</summary>
+        /// <param name="request" type="Object">Object that represents the request to be sent.</param>
+        /// <param name="success" type="Function" optional="true">Callback for a successful read operation.</param>
+        /// <param name="error" type="Function" optional="true">Callback for handling errors.</param>
+        /// <param name="handler" type="Object" optional="true">Handler for data serialization.</param>
+        /// <param name="httpClient" type="Object" optional="true">HTTP client layer.</param>
+        /// <param name="metadata" type="Object" optional="true">Conceptual metadata for this request.</param>
+
+        success = success || odata.defaultSuccess;
+        error = error || odata.defaultError;
+        handler = handler || odata.defaultHandler;
+        httpClient = httpClient || odata.defaultHttpClient;
+        metadata = metadata || odata.defaultMetadata;
+
+        // Augment the request with additional defaults.
+        request.recognizeDates = defined(request.recognizeDates, odata.jsonHandler.recognizeDates);
+        request.callbackParameterName = defined(request.callbackParameterName, odata.defaultHttpClient.callbackParameterName);
+        request.formatQueryString = defined(request.formatQueryString, odata.defaultHttpClient.formatQueryString);
+        request.enableJsonpCallback = defined(request.enableJsonpCallback, odata.defaultHttpClient.enableJsonpCallback);
+        request.useJsonLight = defined(request.useJsonLight, odata.jsonHandler.enableJsonpCallback);
+        request.inferJsonLightFeedAsObject = defined(request.inferJsonLightFeedAsObject, odata.jsonHandler.inferJsonLightFeedAsObject);
+
+        // Create the base context for read/write operations, also specifying complete settings.
+        var context = {
+            metadata: metadata,
+            recognizeDates: request.recognizeDates,
+            callbackParameterName: request.callbackParameterName,
+            formatQueryString: request.formatQueryString,
+            enableJsonpCallback: request.enableJsonpCallback,
+            useJsonLight: request.useJsonLight,
+            inferJsonLightFeedAsObject: request.inferJsonLightFeedAsObject
+        };
+
+        try {
+            prepareRequest(request, handler, context);
+            return invokeRequest(request, success, error, handler, httpClient, context);
+        } catch (err) {
+            error(err);
+        }
+    };
+
+    odata.parseMetadata = function (csdlMetadataDocument) {
+        /// <summary>Parses the csdl metadata to DataJS metatdata format. This method can be used when the metadata is retrieved using something other than DataJS</summary>
+        /// <param name="atomMetadata" type="string">A string that represents the entire csdl metadata.</param>
+        /// <returns type="Object">An object that has the representation of the metadata in Datajs format.</returns>
+
+        return metadataParser(null, csdlMetadataDocument);
+    };
+
+    // Configure the batch handler to use the default handler for the batch parts.
+    odata.batchHandler.partHandler = odata.defaultHandler;
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/store-dom.js b/JSLib/src/store-dom.js
new file mode 100644
index 0000000..bf8a887
--- /dev/null
+++ b/JSLib/src/store-dom.js
@@ -0,0 +1,320 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// store-dom.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+
+    // Imports.
+    var throwErrorCallback = datajs.throwErrorCallback;
+    var delay = datajs.delay;
+
+    // CONTENT START
+
+    var localStorage = null;
+
+    var domStoreDateToJSON = function () {
+        /// <summary>Converts a Date object into an object representation friendly to JSON serialization.</summary>
+        /// <returns type="Object">Object that represents the Date.</returns>
+        /// <remarks>
+        ///   This method is used to override the Date.toJSON method and is called only by
+        ///   JSON.stringify.  It should never be called directly.
+        /// </remarks>
+
+        var newValue = { v: this.valueOf(), t: "[object Date]" };
+        // Date objects might have extra properties on them so we save them.
+        for (var name in this) {
+            newValue[name] = this[name];
+        }
+        return newValue;
+    };
+
+    var domStoreJSONToDate = function (_, value) {
+        /// <summary>JSON reviver function for converting an object representing a Date in a JSON stream to a Date object</summary>
+        /// <param value="Object">Object to convert.</param>
+        /// <returns type="Date">Date object.</returns>
+        /// <remarks>
+        ///   This method is used during JSON parsing and invoked only by the reviver function.
+        ///   It should never be called directly.
+        /// </remarks>
+
+        if (value && value.t === "[object Date]") {
+            var newValue = new Date(value.v);
+            for (var name in value) {
+                if (name !== "t" && name !== "v") {
+                    newValue[name] = value[name];
+                }
+            }
+            value = newValue;
+        }
+        return value;
+    };
+
+    var qualifyDomStoreKey = function (store, key) {
+        /// <summary>Qualifies the key with the name of the store.</summary>
+        /// <param name="store" type="Object">Store object whose name will be used for qualifying the key.</param>
+        /// <param name="key" type="String">Key string.</param>
+        /// <returns type="String">Fully qualified key string.</returns>
+
+        return store.name + "#!#" + key;
+    };
+
+    var unqualifyDomStoreKey = function (store, key) {
+        /// <summary>Gets the key part of a fully qualified key string.</summary>
+        /// <param name="store" type="Object">Store object whose name will be used for qualifying the key.</param>
+        /// <param name="key" type="String">Fully qualified key string.</param>
+        /// <returns type="String">Key part string</returns>
+
+        return key.replace(store.name + "#!#", "");
+    };
+
+    var DomStore = function (name) {
+        /// <summary>Constructor for store objects that use DOM storage as the underlying mechanism.</summary>
+        /// <param name="name" type="String">Store name.</param>
+        this.name = name;
+    };
+
+    DomStore.create = function (name) {
+        /// <summary>Creates a store object that uses DOM Storage as its underlying mechanism.</summary>
+        /// <param name="name" type="String">Store name.</param>
+        /// <returns type="Object">Store object.</returns>
+
+        if (DomStore.isSupported()) {
+            localStorage = localStorage || window.localStorage;
+            return new DomStore(name);
+        }
+
+        throw { message: "Web Storage not supported by the browser" };
+    };
+
+    DomStore.isSupported = function () {
+        /// <summary>Checks whether the underlying mechanism for this kind of store objects is supported by the browser.</summary>
+        /// <returns type="Boolean">True if the mechanism is supported by the browser; otherwise false.</summary>
+        return !!window.localStorage;
+    };
+
+    DomStore.prototype.add = function (key, value, success, error) {
+        /// <summary>Adds a new value identified by a key to the store.</summary>
+        /// <param name="key" type="String">Key string.</param>
+        /// <param name="value">Value that is going to be added to the store.</param>
+        /// <param name="success" type="Function" optional="no">Callback for a successful add operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        /// <remarks>
+        ///    This method errors out if the store already contains the specified key.
+        /// </remarks>
+
+        error = error || this.defaultError;
+        var store = this;
+        this.contains(key, function (contained) {
+            if (!contained) {
+                store.addOrUpdate(key, value, success, error);
+            } else {
+                delay(error, { message: "key already exists", key: key });
+            }
+        }, error);
+    };
+
+    DomStore.prototype.addOrUpdate = function (key, value, success, error) {
+        /// <summary>Adds or updates a value identified by a key to the store.</summary>
+        /// <param name="key" type="String">Key string.</param>
+        /// <param name="value">Value that is going to be added or updated to the store.</param>
+        /// <param name="success" type="Function" optional="no">Callback for a successful add or update operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        /// <remarks>
+        ///   This method will overwrite the key's current value if it already exists in the store; otherwise it simply adds the new key and value.
+        /// </remarks>
+
+        error = error || this.defaultError;
+
+        if (key instanceof Array) {
+            error({ message: "Array of keys not supported" });
+        } else {
+            var fullKey = qualifyDomStoreKey(this, key);
+            var oldDateToJSON = Date.prototype.toJSON;
+            try {
+                var storedValue = value;
+                if (storedValue !== undefined) {
+                    // Dehydrate using json
+                    Date.prototype.toJSON = domStoreDateToJSON;
+                    storedValue = window.JSON.stringify(value);
+                }
+                // Save the json string.
+                localStorage.setItem(fullKey, storedValue);
+                delay(success, key, value);
+            }
+            catch (e) {
+                if (e.code === 22 || e.number === 0x8007000E) {
+                    delay(error, { name: "QUOTA_EXCEEDED_ERR", error: e });
+                } else {
+                    delay(error, e);
+                }
+            }
+            finally {
+                Date.prototype.toJSON = oldDateToJSON;
+            }
+        }
+    };
+
+    DomStore.prototype.clear = function (success, error) {
+        /// <summary>Removes all the data associated with this store object.</summary>
+        /// <param name="success" type="Function" optional="no">Callback for a successful clear operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        /// <remarks>
+        ///    In case of an error, this method will not restore any keys that might have been deleted at that point.
+        /// </remarks>
+
+        error = error || this.defaultError;
+        try {
+            var i = 0, len = localStorage.length;
+            while (len > 0 && i < len) {
+                var fullKey = localStorage.key(i);
+                var key = unqualifyDomStoreKey(this, fullKey);
+                if (fullKey !== key) {
+                    localStorage.removeItem(fullKey);
+                    len = localStorage.length;
+                } else {
+                    i++;
+                }
+            }
+            delay(success);
+        }
+        catch (e) {
+            delay(error, e);
+        }
+    };
+
+    DomStore.prototype.close = function () {
+        /// <summary>This function does nothing in DomStore as it does not have a connection model</summary>
+    };
+
+    DomStore.prototype.contains = function (key, success, error) {
+        /// <summary>Checks whether a key exists in the store.</summary>
+        /// <param name="key" type="String">Key string.</param>
+        /// <param name="success" type="Function" optional="no">Callback indicating whether the store contains the key or not.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        error = error || this.defaultError;
+        try {
+            var fullKey = qualifyDomStoreKey(this, key);
+            var value = localStorage.getItem(fullKey);
+            delay(success, value !== null);
+        } catch (e) {
+            delay(error, e);
+        }
+    };
+
+    DomStore.prototype.defaultError = throwErrorCallback;
+
+    DomStore.prototype.getAllKeys = function (success, error) {
+        /// <summary>Gets all the keys that exist in the store.</summary>
+        /// <param name="success" type="Function" optional="no">Callback for a successful get operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+
+        error = error || this.defaultError;
+
+        var results = [];
+        var i, len;
+
+        try {
+            for (i = 0, len = localStorage.length; i < len; i++) {
+                var fullKey = localStorage.key(i);
+                var key = unqualifyDomStoreKey(this, fullKey);
+                if (fullKey !== key) {
+                    results.push(key);
+                }
+            }
+            delay(success, results);
+        }
+        catch (e) {
+            delay(error, e);
+        }
+    };
+
+    /// <summary>Identifies the underlying mechanism used by the store.</summary>
+    DomStore.prototype.mechanism = "dom";
+
+    DomStore.prototype.read = function (key, success, error) {
+        /// <summary>Reads the value associated to a key in the store.</summary>
+        /// <param name="key" type="String">Key string.</param>
+        /// <param name="success" type="Function" optional="no">Callback for a successful reads operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        error = error || this.defaultError;
+
+        if (key instanceof Array) {
+            error({ message: "Array of keys not supported" });
+        } else {
+            try {
+                var fullKey = qualifyDomStoreKey(this, key);
+                var value = localStorage.getItem(fullKey);
+                if (value !== null && value !== "undefined") {
+                    // Hydrate using json
+                    value = window.JSON.parse(value, domStoreJSONToDate);
+                }
+                else {
+                    value = undefined;
+                }
+                delay(success, key, value);
+            } catch (e) {
+                delay(error, e);
+            }
+        }
+    };
+
+    DomStore.prototype.remove = function (key, success, error) {
+        /// <summary>Removes a key and its value from the store.</summary>
+        /// <param name="key" type="String">Key string.</param>
+        /// <param name="success" type="Function" optional="no">Callback for a successful remove operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        error = error || this.defaultError;
+
+        if (key instanceof Array) {
+            error({ message: "Batches not supported" });
+        } else {
+            try {
+                var fullKey = qualifyDomStoreKey(this, key);
+                localStorage.removeItem(fullKey);
+                delay(success);
+            } catch (e) {
+                delay(error, e);
+            }
+        }
+    };
+
+    DomStore.prototype.update = function (key, value, success, error) {
+        /// <summary>Updates the value associated to a key in the store.</summary>
+        /// <param name="key" type="String">Key string.</param>
+        /// <param name="value">New value.</param>
+        /// <param name="success" type="Function" optional="no">Callback for a successful update operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        /// <remarks>
+        ///    This method errors out if the specified key is not found in the store.
+        /// </remarks>
+
+        error = error || this.defaultError;
+        var store = this;
+        this.contains(key, function (contained) {
+            if (contained) {
+                store.addOrUpdate(key, value, success, error);
+            } else {
+                delay(error, { message: "key not found", key: key });
+            }
+        }, error);
+    };
+
+    // DATAJS INTERNAL START
+    datajs.DomStore = DomStore;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/store-indexeddb.js b/JSLib/src/store-indexeddb.js
new file mode 100644
index 0000000..b56828f
--- /dev/null
+++ b/JSLib/src/store-indexeddb.js
@@ -0,0 +1,417 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// store-indexeddb.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+
+    // Imports.
+    var throwErrorCallback = datajs.throwErrorCallback;
+    var delay = datajs.delay;
+
+    // CONTENT START
+
+    var indexedDB = window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.indexedDB;
+    var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
+    var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || {};
+
+    var IDBT_READ_ONLY = IDBTransaction.READ_ONLY || "readonly";
+    var IDBT_READ_WRITE = IDBTransaction.READ_WRITE || "readwrite";
+
+    var getError = function (error, defaultError) {
+        /// <summary>Returns either a specific error handler or the default error handler</summary>
+        /// <param name="error" type="Function">The specific error handler</param>
+        /// <param name="defaultError" type="Function">The default error handler</param>
+        /// <returns type="Function">The error callback</returns>
+
+        return function (e) {
+            var errorFunc = error || defaultError;
+            if (!errorFunc) {
+                return;
+            }
+
+            // Old api quota exceeded error support.
+            if (Object.prototype.toString.call(e) === "[object IDBDatabaseException]") {
+                if (e.code === 11 /* IndexedDb disk quota exceeded */) {
+                    errorFunc({ name: "QuotaExceededError", error: e });
+                    return;
+                }
+                errorFunc(e);
+                return;
+            }
+
+            var errName;
+            try {
+                var errObj = e.target.error || e;
+                errName = errObj.name;
+            } catch (ex) {
+                errName = (e.type === "blocked") ? "IndexedDBBlocked" : "UnknownError";
+            }
+            errorFunc({ name: errName, error: e });
+        };
+    };
+
+    var openStoreDb = function (store, success, error) {
+        /// <summary>Opens the store object's indexed db database.</summary>
+        /// <param name="store" type="IndexedDBStore">The store object</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+
+        var storeName = store.name;
+        var dbName = "_datajs_" + storeName;
+
+        var request = indexedDB.open(dbName);
+        request.onblocked = error;
+        request.onerror = error;
+
+        request.onupgradeneeded = function () {
+            var db = request.result;
+            if (!db.objectStoreNames.contains(storeName)) {
+                db.createObjectStore(storeName);
+            }
+        };
+
+        request.onsuccess = function (event) {
+            var db = request.result;
+            if (!db.objectStoreNames.contains(storeName)) {
+                // Should we use the old style api to define the database schema?
+                if ("setVersion" in db) {
+                    var versionRequest = db.setVersion("1.0");
+                    versionRequest.onsuccess = function () {
+                        var transaction = versionRequest.transaction;
+                        transaction.oncomplete = function () {
+                            success(db);
+                        };
+                        db.createObjectStore(storeName, null, false);
+                    };
+                    versionRequest.onerror = error;
+                    versionRequest.onblocked = error;
+                    return;
+                }
+
+                // The database doesn't have the expected store.
+                // Fabricate an error object for the event for the schema mismatch
+                // and error out.
+                event.target.error = { name: "DBSchemaMismatch" };
+                error(event);
+                return;
+            }
+
+            db.onversionchange = function(event) {
+                event.target.close();
+            };
+            success(db);
+        };
+    };
+
+    var openTransaction = function (store, mode, success, error) {
+        /// <summary>Opens a new transaction to the store</summary>
+        /// <param name="store" type="IndexedDBStore">The store object</param>
+        /// <param name="mode" type="Short">The read/write mode of the transaction (constants from IDBTransaction)</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+
+        var storeName = store.name;
+        var storeDb = store.db;
+        var errorCallback = getError(error, store.defaultError);
+
+        if (storeDb) {
+            success(storeDb.transaction(storeName, mode));
+            return;
+        }
+
+        openStoreDb(store, function (db) {
+            store.db = db;
+            success(db.transaction(storeName, mode));
+        }, errorCallback);
+    };
+
+    var IndexedDBStore = function (name) {
+        /// <summary>Creates a new IndexedDBStore.</summary>
+        /// <param name="name" type="String">The name of the store.</param>
+        /// <returns type="Object">The new IndexedDBStore.</returns>
+        this.name = name;
+    };
+
+    IndexedDBStore.create = function (name) {
+        /// <summary>Creates a new IndexedDBStore.</summary>
+        /// <param name="name" type="String">The name of the store.</param>
+        /// <returns type="Object">The new IndexedDBStore.</returns>
+        if (IndexedDBStore.isSupported()) {
+            return new IndexedDBStore(name);
+        }
+
+        throw { message: "IndexedDB is not supported on this browser" };
+    };
+
+    IndexedDBStore.isSupported = function () {
+        /// <summary>Returns whether IndexedDB is supported.</summary>
+        /// <returns type="Boolean">True if IndexedDB is supported, false otherwise.</returns>
+        return !!indexedDB;
+    };
+
+    IndexedDBStore.prototype.add = function (key, value, success, error) {
+        /// <summary>Adds a key/value pair to the store</summary>
+        /// <param name="key" type="String">The key</param>
+        /// <param name="value" type="Object">The value</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        var keys = [];
+        var values = [];
+
+        if (key instanceof Array) {
+            keys = key;
+            values = value;
+        } else {
+            keys = [key];
+            values = [value];
+        }
+
+        openTransaction(this, IDBT_READ_WRITE, function (transaction) {
+            transaction.onabort = getError(error, defaultError, key, "add");
+            transaction.oncomplete = function () {
+                if (key instanceof Array) {
+                    success(keys, values);
+                } else {
+                    success(key, value);
+                }
+            };
+
+            for (var i = 0; i < keys.length && i < values.length; i++) {
+                transaction.objectStore(name).add({ v: values[i] }, keys[i]);
+            }
+        }, error);
+    };
+
+    IndexedDBStore.prototype.addOrUpdate = function (key, value, success, error) {
+        /// <summary>Adds or updates a key/value pair in the store</summary>
+        /// <param name="key" type="String">The key</param>
+        /// <param name="value" type="Object">The value</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        var keys = [];
+        var values = [];
+
+        if (key instanceof Array) {
+            keys = key;
+            values = value;
+        } else {
+            keys = [key];
+            values = [value];
+        }
+
+        openTransaction(this, IDBT_READ_WRITE, function (transaction) {
+            transaction.onabort = getError(error, defaultError);
+            transaction.oncomplete = function () {
+                if (key instanceof Array) {
+                    success(keys, values);
+                } else {
+                    success(key, value);
+                }
+            };
+
+            for (var i = 0; i < keys.length && i < values.length; i++) {
+                var record = { v: values[i] };
+                transaction.objectStore(name).put(record, keys[i]);
+            }
+        }, error);
+    };
+
+    IndexedDBStore.prototype.clear = function (success, error) {
+        /// <summary>Clears the store</summary>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        openTransaction(this, IDBT_READ_WRITE, function (transaction) {
+            transaction.onerror = getError(error, defaultError);
+            transaction.oncomplete = function () {
+                success();
+            };
+
+            transaction.objectStore(name).clear();
+        }, error);
+    };
+
+    IndexedDBStore.prototype.close = function () {
+        /// <summary>Closes the connection to the database</summary>
+        if (this.db) {
+            this.db.close();
+            this.db = null;
+        }
+    };
+
+    IndexedDBStore.prototype.contains = function (key, success, error) {
+        /// <summary>Returns whether the store contains a key</summary>
+        /// <param name="key" type="String">The key</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        openTransaction(this, IDBT_READ_ONLY, function (transaction) {
+            var objectStore = transaction.objectStore(name);
+            var request = objectStore["get"](key);
+
+            transaction.oncomplete = function () {
+                success(!!request.result);
+            };
+            transaction.onerror = getError(error, defaultError);
+        }, error);
+    };
+
+    IndexedDBStore.prototype.defaultError = throwErrorCallback;
+
+    IndexedDBStore.prototype.getAllKeys = function (success, error) {
+        /// <summary>Gets all the keys from the store</summary>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        openTransaction(this, IDBT_READ_WRITE, function (transaction) {
+            var results = [];
+
+            transaction.oncomplete = function () {
+                success(results);
+            };
+
+            var request = transaction.objectStore(name).openCursor();
+
+            request.onerror = getError(error, defaultError);
+            request.onsuccess = function (event) {
+                var cursor = event.target.result;
+                if (cursor) {
+                    results.push(cursor.key);
+                    // Some tools have issues because continue is a javascript reserved word.
+                    cursor["continue"].call(cursor);
+                }
+            };
+        }, error);
+    };
+
+    /// <summary>Identifies the underlying mechanism used by the store.</summary>
+    IndexedDBStore.prototype.mechanism = "indexeddb";
+
+    IndexedDBStore.prototype.read = function (key, success, error) {
+        /// <summary>Reads the value for the specified key</summary>
+        /// <param name="key" type="String">The key</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        /// <remarks>If the key does not exist, the success handler will be called with value = undefined</remarks>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        var keys = (key instanceof Array) ? key : [key];
+
+        openTransaction(this, IDBT_READ_ONLY, function (transaction) {
+            var values = [];
+
+            transaction.onerror = getError(error, defaultError, key, "read");
+            transaction.oncomplete = function () {
+                if (key instanceof Array) {
+                    success(keys, values);
+                } else {
+                    success(keys[0], values[0]);
+                }
+            };
+
+            for (var i = 0; i < keys.length; i++) {
+                // Some tools have issues because get is a javascript reserved word. 
+                var objectStore = transaction.objectStore(name);
+                var request = objectStore["get"].call(objectStore, keys[i]);
+                request.onsuccess = function (event) {
+                    var record = event.target.result;
+                    values.push(record ? record.v : undefined);
+                };
+            }
+        }, error);
+    };
+
+    IndexedDBStore.prototype.remove = function (key, success, error) {
+        /// <summary>Removes the specified key from the store</summary>
+        /// <param name="key" type="String">The key</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        var keys = (key instanceof Array) ? key : [key];
+
+        openTransaction(this, IDBT_READ_WRITE, function (transaction) {
+            transaction.onerror = getError(error, defaultError);
+            transaction.oncomplete = function () {
+                success();
+            };
+
+            for (var i = 0; i < keys.length; i++) {
+                // Some tools have issues because continue is a javascript reserved word.
+                var objectStore = transaction.objectStore(name);
+                objectStore["delete"].call(objectStore, keys[i]);
+            }
+        }, error);
+    };
+
+    IndexedDBStore.prototype.update = function (key, value, success, error) {
+        /// <summary>Updates a key/value pair in the store</summary>
+        /// <param name="key" type="String">The key</param>
+        /// <param name="value" type="Object">The value</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        var keys = [];
+        var values = [];
+
+        if (key instanceof Array) {
+            keys = key;
+            values = value;
+        } else {
+            keys = [key];
+            values = [value];
+        }
+
+        openTransaction(this, IDBT_READ_WRITE, function (transaction) {
+            transaction.onabort = getError(error, defaultError);
+            transaction.oncomplete = function () {
+                if (key instanceof Array) {
+                    success(keys, values);
+                } else {
+                    success(key, value);
+                }
+            };
+
+            for (var i = 0; i < keys.length && i < values.length; i++) {
+                var request = transaction.objectStore(name).openCursor(IDBKeyRange.only(keys[i]));
+                var record = { v: values[i] };
+                request.pair = { key: keys[i], value: record };
+                request.onsuccess = function (event) {
+                    var cursor = event.target.result;
+                    if (cursor) {
+                        cursor.update(event.target.pair.value);
+                    } else {
+                        transaction.abort();
+                    }
+                };
+            }
+        }, error);
+    };
+
+    // DATAJS INTERNAL START
+    datajs.IndexedDBStore = IndexedDBStore;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/store-memory.js b/JSLib/src/store-memory.js
new file mode 100644
index 0000000..4f35090
--- /dev/null
+++ b/JSLib/src/store-memory.js
@@ -0,0 +1,231 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// store-memory.js 
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+
+    // Imports.
+    var throwErrorCallback = datajs.throwErrorCallback;
+    var delay = datajs.delay;
+
+    // CONTENT START
+
+    var MemoryStore = function (name) {
+        /// <summary>Constructor for store objects that use a sorted array as the underlying mechanism.</summary>
+        /// <param name="name" type="String">Store name.</param>
+
+        var holes = [];
+        var items = [];
+        var keys = {};
+
+        this.name = name;
+
+        var getErrorCallback = function (error) {
+            return error || this.defaultError;
+        };
+
+        var validateKeyInput = function (key, error) {
+            /// <summary>Validates that the specified key is not undefined, not null, and not an array</summary>
+            /// <param name="key">Key value.</param>
+            /// <param name="error" type="Function">Error callback.</param>
+            /// <returns type="Boolean">True if the key is valid. False if the key is invalid and the error callback has been queued for execution.</returns>
+
+            var messageString;
+
+            if (key instanceof Array) {
+                messageString = "Array of keys not supported";
+            }
+
+            if (key === undefined || key === null) {
+                messageString = "Invalid key";
+            }
+
+            if (messageString) {
+                delay(error, { message: messageString });
+                return false;
+            }
+            return true;
+        };
+
+        this.add = function (key, value, success, error) {
+            /// <summary>Adds a new value identified by a key to the store.</summary>
+            /// <param name="key" type="String">Key string.</param>
+            /// <param name="value">Value that is going to be added to the store.</param>
+            /// <param name="success" type="Function" optional="no">Callback for a successful add operation.</param>
+            /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+            /// <remarks>
+            ///    This method errors out if the store already contains the specified key.
+            /// </remarks>
+
+            error = getErrorCallback(error);
+
+            if (validateKeyInput(key, error)) {
+                if (!keys.hasOwnProperty(key)) {
+                    this.addOrUpdate(key, value, success, error);
+                } else {
+                    error({ message: "key already exists", key: key });
+                }
+            }
+        };
+
+        this.addOrUpdate = function (key, value, success, error) {
+            /// <summary>Adds or updates a value identified by a key to the store.</summary>
+            /// <param name="key" type="String">Key string.</param>
+            /// <param name="value">Value that is going to be added or updated to the store.</param>
+            /// <param name="success" type="Function" optional="no">Callback for a successful add or update operation.</param>
+            /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+            /// <remarks>
+            ///   This method will overwrite the key's current value if it already exists in the store; otherwise it simply adds the new key and value.
+            /// </remarks>
+
+            error = getErrorCallback(error);
+
+            if (validateKeyInput(key, error)) {
+                var index = keys[key];
+                if (index === undefined) {
+                    if (holes.length > 0) {
+                        index = holes.splice(0, 1);
+                    } else {
+                        index = items.length;
+                    }
+                }
+                items[index] = value;
+                keys[key] = index;
+                delay(success, key, value);
+            }
+        };
+
+        this.clear = function (success) {
+            /// <summary>Removes all the data associated with this store object.</summary>
+            /// <param name="success" type="Function" optional="no">Callback for a successful clear operation.</param>
+
+            items = [];
+            keys = {};
+            holes = [];
+
+            delay(success);
+        };
+
+        this.contains = function (key, success) {
+            /// <summary>Checks whether a key exists in the store.</summary>
+            /// <param name="key" type="String">Key string.</param>
+            /// <param name="success" type="Function" optional="no">Callback indicating whether the store contains the key or not.</param>
+
+            var contained = keys.hasOwnProperty(key);
+            delay(success, contained);
+        };
+
+        this.getAllKeys = function (success) {
+            /// <summary>Gets all the keys that exist in the store.</summary>
+            /// <param name="success" type="Function" optional="no">Callback for a successful get operation.</param>
+
+            var results = [];
+            for (var name in keys) {
+                results.push(name);
+            }
+            delay(success, results);
+        };
+
+        this.read = function (key, success, error) {
+            /// <summary>Reads the value associated to a key in the store.</summary>
+            /// <param name="key" type="String">Key string.</param>
+            /// <param name="success" type="Function" optional="no">Callback for a successful reads operation.</param>
+            /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+            error = getErrorCallback(error);
+
+            if (validateKeyInput(key, error)) {
+                var index = keys[key];
+                delay(success, key, items[index]);
+            }
+        };
+
+        this.remove = function (key, success, error) {
+            /// <summary>Removes a key and its value from the store.</summary>
+            /// <param name="key" type="String">Key string.</param>
+            /// <param name="success" type="Function" optional="no">Callback for a successful remove operation.</param>
+            /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+            error = getErrorCallback(error);
+
+            if (validateKeyInput(key, error)) {
+                var index = keys[key];
+                if (index !== undefined) {
+                    if (index === items.length - 1) {
+                        items.pop();
+                    } else {
+                        items[index] = undefined;
+                        holes.push(index);
+                    }
+                    delete keys[key];
+
+                    // The last item was removed, no need to keep track of any holes in the array.
+                    if (items.length === 0) {
+                        holes = [];
+                    }
+                }
+
+                delay(success);
+            }
+        };
+
+        this.update = function (key, value, success, error) {
+            /// <summary>Updates the value associated to a key in the store.</summary>
+            /// <param name="key" type="String">Key string.</param>
+            /// <param name="value">New value.</param>
+            /// <param name="success" type="Function" optional="no">Callback for a successful update operation.</param>
+            /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+            /// <remarks>
+            ///    This method errors out if the specified key is not found in the store.
+            /// </remarks>
+
+            error = getErrorCallback(error);
+            if (validateKeyInput(key, error)) {
+                if (keys.hasOwnProperty(key)) {
+                    this.addOrUpdate(key, value, success, error);
+                } else {
+                    error({ message: "key not found", key: key });
+                }
+            }
+        };
+    };
+
+    MemoryStore.create = function (name) {
+        /// <summary>Creates a store object that uses memory storage as its underlying mechanism.</summary>
+        /// <param name="name" type="String">Store name.</param>
+        /// <returns type="Object">Store object.</returns>
+        return new MemoryStore(name);
+    };
+
+    MemoryStore.isSupported = function () {
+        /// <summary>Checks whether the underlying mechanism for this kind of store objects is supported by the browser.</summary>
+        /// <returns type="Boolean">True if the mechanism is supported by the browser; otherwise false.</returns>
+        return true;
+    };
+
+    MemoryStore.prototype.close = function () {
+        /// <summary>This function does nothing in MemoryStore as it does not have a connection model.</summary>
+    };
+
+    MemoryStore.prototype.defaultError = throwErrorCallback;
+
+    /// <summary>Identifies the underlying mechanism used by the store.</summary>
+    MemoryStore.prototype.mechanism = "memory";
+
+    // DATAJS INTERNAL START
+    datajs.MemoryStore = MemoryStore;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/store.js b/JSLib/src/store.js
new file mode 100644
index 0000000..688e88e
--- /dev/null
+++ b/JSLib/src/store.js
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// store.js 
+
+(function (window, undefined) {
+   
+    var datajs = window.datajs || {};
+
+    var DomStore = datajs.DomStore;
+    var IndexedDBStore = datajs.IndexedDBStore;
+    var MemoryStore = datajs.MemoryStore;
+
+    // CONTENT START
+
+    var mechanisms = {
+        indexeddb: IndexedDBStore,
+        dom: DomStore,
+        memory: MemoryStore
+    };
+
+    datajs.defaultStoreMechanism = "best";
+
+    datajs.createStore = function (name, mechanism) {
+        /// <summary>Creates a new store object.</summary>
+        /// <param name="name" type="String">Store name.</param>
+        /// <param name="mechanism" type="String" optional="true">A specific mechanism to use (defaults to best, can be "best", "dom", "indexeddb", "webdb").</param>
+        /// <returns type="Object">Store object.</returns>
+
+        if (!mechanism) {
+            mechanism = datajs.defaultStoreMechanism;
+        }
+
+        if (mechanism === "best") {
+            mechanism = (DomStore.isSupported()) ? "dom" : "memory";
+        }
+
+        var factory = mechanisms[mechanism];
+        if (factory) {
+            return factory.create(name);
+        }
+
+        throw { message: "Failed to create store", name: name, mechanism: mechanism };
+    };
+
+    // DATAJS INTERNAL START
+    datajs.mechanisms = mechanisms;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/utils.js b/JSLib/src/utils.js
new file mode 100644
index 0000000..3dd3a49
--- /dev/null
+++ b/JSLib/src/utils.js
@@ -0,0 +1,490 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// utils.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+
+    // CONTENT START
+
+    var activeXObject = function (progId) {
+        /// <summary>Creates a new ActiveXObject from the given progId.</summary>
+        /// <param name="progId" type="String" mayBeNull="false" optional="false">
+        ///    ProgId string of the desired ActiveXObject.
+        /// </param>
+        /// <remarks>
+        ///    This function throws whatever exception might occur during the creation
+        ///    of the ActiveXObject.
+        /// </remarks>
+        /// <returns type="Object">
+        ///     The ActiveXObject instance. Null if ActiveX is not supported by the
+        ///     browser.
+        /// </returns>
+        if (window.ActiveXObject) {
+            return new window.ActiveXObject(progId);
+        }
+        return null;
+    };
+
+    var assigned = function (value) {
+        /// <summary>Checks whether the specified value is different from null and undefined.</summary>
+        /// <param name="value" mayBeNull="true" optional="true">Value to check.</param>
+        /// <returns type="Boolean">true if the value is assigned; false otherwise.</returns>
+        return value !== null && value !== undefined;
+    };
+
+    var contains = function (arr, item) {
+        /// <summary>Checks whether the specified item is in the array.</summary>
+        /// <param name="arr" type="Array" optional="false" mayBeNull="false">Array to check in.</param>
+        /// <param name="item">Item to look for.</param>
+        /// <returns type="Boolean">true if the item is contained, false otherwise.</returns>
+
+        var i, len;
+        for (i = 0, len = arr.length; i < len; i++) {
+            if (arr[i] === item) {
+                return true;
+            }
+        }
+
+        return false;
+    };
+
+    var defined = function (a, b) {
+        /// <summary>Given two values, picks the first one that is not undefined.</summary>
+        /// <param name="a">First value.</param>
+        /// <param name="b">Second value.</param>
+        /// <returns>a if it's a defined value; else b.</returns>
+        return (a !== undefined) ? a : b;
+    };
+
+    var delay = function (callback) {
+        /// <summary>Delays the invocation of the specified function until execution unwinds.</summary>
+        /// <param name="callback" type="Function">Callback function.</param>
+        if (arguments.length === 1) {
+            window.setTimeout(callback, 0);
+            return;
+        }
+
+        var args = Array.prototype.slice.call(arguments, 1);
+        window.setTimeout(function () {
+            callback.apply(this, args);
+        }, 0);
+    };
+
+    // DATAJS INTERNAL START
+    var djsassert = function (condition, message, data) {
+        /// <summary>Throws an exception in case that a condition evaluates to false.</summary>
+        /// <param name="condition" type="Boolean">Condition to evaluate.</param>
+        /// <param name="message" type="String">Message explaining the assertion.</param>
+        /// <param name="data" type="Object">Additional data to be included in the exception.</param>
+
+        if (!condition) {
+            throw { message: "Assert fired: " + message, data: data };
+        };
+    };
+    // DATAJS INTERNAL END
+
+    var extend = function (target, values) {
+        /// <summary>Extends the target with the specified values.</summary>
+        /// <param name="target" type="Object">Object to add properties to.</param>
+        /// <param name="values" type="Object">Object with properties to add into target.</param>
+        /// <returns type="Object">The target object.</returns>
+
+        for (var name in values) {
+            target[name] = values[name];
+        }
+
+        return target;
+    };
+
+    var find = function (arr, callback) {
+        /// <summary>Returns the first item in the array that makes the callback function true.</summary>
+        /// <param name="arr" type="Array" optional="false" mayBeNull="true">Array to check in.</param>
+        /// <param name="callback" type="Function">Callback function to invoke once per item in the array.</param>
+        /// <returns>The first item that makes the callback return true; null otherwise or if the array is null.</returns>
+
+        if (arr) {
+            var i, len;
+            for (i = 0, len = arr.length; i < len; i++) {
+                if (callback(arr[i])) {
+                    return arr[i];
+                }
+            }
+        }
+        return null;
+    };
+
+    var isArray = function (value) {
+        /// <summary>Checks whether the specified value is an array object.</summary>
+        /// <param name="value">Value to check.</param>
+        /// <returns type="Boolean">true if the value is an array object; false otherwise.</returns>
+
+        return Object.prototype.toString.call(value) === "[object Array]";
+    };
+
+    var isDate = function (value) {
+        /// <summary>Checks whether the specified value is a Date object.</summary>
+        /// <param name="value">Value to check.</param>
+        /// <returns type="Boolean">true if the value is a Date object; false otherwise.</returns>
+
+        return Object.prototype.toString.call(value) === "[object Date]";
+    };
+
+    var isObject = function (value) {
+        /// <summary>Tests whether a value is an object.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <remarks>
+        ///     Per javascript rules, null and array values are objects and will cause this function to return true.
+        /// </remarks>
+        /// <returns type="Boolean">True is the value is an object; false otherwise.</returns>
+
+        return typeof value === "object";
+    };
+
+    var parseInt10 = function (value) {
+        /// <summary>Parses a value in base 10.</summary>
+        /// <param name="value" type="String">String value to parse.</param>
+        /// <returns type="Number">The parsed value, NaN if not a valid value.</returns>
+
+        return parseInt(value, 10);
+    };
+
+    var renameProperty = function (obj, oldName, newName) {
+        /// <summary>Renames a property in an object.</summary>
+        /// <param name="obj" type="Object">Object in which the property will be renamed.</param>
+        /// <param name="oldName" type="String">Name of the property that will be renamed.</param>
+        /// <param name="newName" type="String">New name of the property.</param>
+        /// <remarks>
+        ///    This function will not do anything if the object doesn't own a property with the specified old name.
+        /// </remarks>
+
+        if (obj.hasOwnProperty(oldName)) {
+            obj[newName] = obj[oldName];
+            delete obj[oldName];
+        }
+    };
+
+    var throwErrorCallback = function (error) {
+        /// <summary>Default error handler.</summary>
+        /// <param name="error" type="Object">Error to handle.</param>
+        throw error;
+    };
+
+    var trimString = function (str) {
+        /// <summary>Removes leading and trailing whitespaces from a string.</summary>
+        /// <param name="str" type="String" optional="false" mayBeNull="false">String to trim</param>
+        /// <returns type="String">The string with no leading or trailing whitespace.</returns>
+
+        if (str.trim) {
+            return str.trim();
+        }
+
+        return str.replace(/^\s+|\s+$/g, '');
+    };
+
+    var undefinedDefault = function (value, defaultValue) {
+        /// <summary>Returns a default value in place of undefined.</summary>
+        /// <param name="value" mayBeNull="true" optional="true">Value to check.</param>
+        /// <param name="defaultValue">Value to return if value is undefined.</param>
+        /// <returns>value if it's defined; defaultValue otherwise.</returns>
+        /// <remarks>
+        /// This should only be used for cases where falsy values are valid;
+        /// otherwise the pattern should be 'x = (value) ? value : defaultValue;'.
+        /// </remarks>
+        return (value !== undefined) ? value : defaultValue;
+    };
+
+    // Regular expression that splits a uri into its components:
+    // 0 - is the matched string.
+    // 1 - is the scheme.
+    // 2 - is the authority.
+    // 3 - is the path.
+    // 4 - is the query.
+    // 5 - is the fragment.
+    var uriRegEx = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#:]+)?(\?[^#]*)?(#.*)?/;
+    var uriPartNames = ["scheme", "authority", "path", "query", "fragment"];
+
+    var getURIInfo = function (uri) {
+        /// <summary>Gets information about the components of the specified URI.</summary>
+        /// <param name="uri" type="String">URI to get information from.</param>
+        /// <returns type="Object">
+        /// An object with an isAbsolute flag and part names (scheme, authority, etc.) if available.
+        /// </returns>
+
+        var result = { isAbsolute: false };
+
+        if (uri) {
+            var matches = uriRegEx.exec(uri);
+            if (matches) {
+                var i, len;
+                for (i = 0, len = uriPartNames.length; i < len; i++) {
+                    if (matches[i + 1]) {
+                        result[uriPartNames[i]] = matches[i + 1];
+                    }
+                }
+            }
+            if (result.scheme) {
+                result.isAbsolute = true;
+            }
+        }
+
+        return result;
+    };
+
+    var getURIFromInfo = function (uriInfo) {
+        /// <summary>Builds a URI string from its components.</summary>
+        /// <param name="uriInfo" type="Object"> An object with uri parts (scheme, authority, etc.).</param>
+        /// <returns type="String">URI string.</returns>
+
+        return "".concat(
+            uriInfo.scheme || "",
+            uriInfo.authority || "",
+            uriInfo.path || "",
+            uriInfo.query || "",
+            uriInfo.fragment || "");
+    };
+
+    // Regular expression that splits a uri authority into its subcomponents:
+    // 0 - is the matched string.
+    // 1 - is the userinfo subcomponent.
+    // 2 - is the host subcomponent.
+    // 3 - is the port component.
+    var uriAuthorityRegEx = /^\/{0,2}(?:([^@]*)@)?([^:]+)(?::{1}(\d+))?/;
+
+    // Regular expression that matches percentage enconded octects (i.e %20 or %3A);
+    var pctEncodingRegEx = /%[0-9A-F]{2}/ig;
+
+    var normalizeURICase = function (uri) {
+        /// <summary>Normalizes the casing of a URI.</summary>
+        /// <param name="uri" type="String">URI to normalize, absolute or relative.</param>
+        /// <returns type="String">The URI normalized to lower case.</returns>
+
+        var uriInfo = getURIInfo(uri);
+        var scheme = uriInfo.scheme;
+        var authority = uriInfo.authority;
+
+        if (scheme) {
+            uriInfo.scheme = scheme.toLowerCase();
+            if (authority) {
+                var matches = uriAuthorityRegEx.exec(authority);
+                if (matches) {
+                    uriInfo.authority = "//" +
+                    (matches[1] ? matches[1] + "@" : "") +
+                    (matches[2].toLowerCase()) +
+                    (matches[3] ? ":" + matches[3] : "");
+                }
+            }
+        }
+
+        uri = getURIFromInfo(uriInfo);
+
+        return uri.replace(pctEncodingRegEx, function (str) {
+            return str.toLowerCase();
+        });
+    };
+
+    var normalizeURI = function (uri, base) {
+        /// <summary>Normalizes a possibly relative URI with a base URI.</summary>
+        /// <param name="uri" type="String">URI to normalize, absolute or relative.</param>
+        /// <param name="base" type="String" mayBeNull="true">Base URI to compose with.</param>
+        /// <returns type="String">The composed URI if relative; the original one if absolute.</returns>
+
+        if (!base) {
+            return uri;
+        }
+
+        var uriInfo = getURIInfo(uri);
+        if (uriInfo.isAbsolute) {
+            return uri;
+        }
+
+        var baseInfo = getURIInfo(base);
+        var normInfo = {};
+        var path;
+
+        if (uriInfo.authority) {
+            normInfo.authority = uriInfo.authority;
+            path = uriInfo.path;
+            normInfo.query = uriInfo.query;
+        } else {
+            if (!uriInfo.path) {
+                path = baseInfo.path;
+                normInfo.query = uriInfo.query || baseInfo.query;
+            } else {
+                if (uriInfo.path.charAt(0) === '/') {
+                    path = uriInfo.path;
+                } else {
+                    path = mergeUriPathWithBase(uriInfo.path, baseInfo.path);
+                }
+                normInfo.query = uriInfo.query;
+            }
+            normInfo.authority = baseInfo.authority;
+        }
+
+        normInfo.path = removeDotsFromPath(path);
+
+        normInfo.scheme = baseInfo.scheme;
+        normInfo.fragment = uriInfo.fragment;
+
+        return getURIFromInfo(normInfo);
+    };
+
+    var mergeUriPathWithBase = function (uriPath, basePath) {
+        /// <summary>Merges the path of a relative URI and a base URI.</summary>
+        /// <param name="uriPath" type="String>Relative URI path.</param>
+        /// <param name="basePath" type="String">Base URI path.</param>
+        /// <returns type="String">A string with the merged path.</returns>
+
+        var path = "/";
+        var end;
+
+        if (basePath) {
+            end = basePath.lastIndexOf("/");
+            path = basePath.substring(0, end);
+
+            if (path.charAt(path.length - 1) !== "/") {
+                path = path + "/";
+            }
+        }
+
+        return path + uriPath;
+    };
+
+    var removeDotsFromPath = function (path) {
+        /// <summary>Removes the special folders . and .. from a URI's path.</summary>
+        /// <param name="path" type="string">URI path component.</param>
+        /// <returns type="String">Path without any . and .. folders.</returns>
+
+        var result = "";
+        var segment = "";
+        var end;
+
+        while (path) {
+            if (path.indexOf("..") === 0 || path.indexOf(".") === 0) {
+                path = path.replace(/^\.\.?\/?/g, "");
+            } else if (path.indexOf("/..") === 0) {
+                path = path.replace(/^\/\..\/?/g, "/");
+                end = result.lastIndexOf("/");
+                if (end === -1) {
+                    result = "";
+                } else {
+                    result = result.substring(0, end);
+                }
+            } else if (path.indexOf("/.") === 0) {
+                path = path.replace(/^\/\.\/?/g, "/");
+            } else {
+                segment = path;
+                end = path.indexOf("/", 1);
+                if (end !== -1) {
+                    segment = path.substring(0, end);
+                }
+                result = result + segment;
+                path = path.replace(segment, "");
+            }
+        }
+        return result;
+    };
+
+    var convertByteArrayToHexString = function (str) {
+        var arr = [];
+        if (window.atob === undefined) {
+            arr = decodeBase64(str);
+        } else {
+            var binaryStr = window.atob(str);
+            for (var i = 0; i < binaryStr.length; i++) {
+                arr.push(binaryStr.charCodeAt(i));
+            }
+        }
+        var hexValue = "";
+        var hexValues = "0123456789ABCDEF";
+        for (var j = 0; j < arr.length; j++) {
+            var t = arr[j];
+            hexValue += hexValues[t >> 4];
+            hexValue += hexValues[t & 0x0F];
+        }
+        return hexValue;
+    };
+
+    var decodeBase64 = function (str) {
+        var binaryString = "";
+        for (var i = 0; i < str.length; i++) {
+            var base65IndexValue = getBase64IndexValue(str[i]);
+            var binaryValue = "";
+            if (base65IndexValue !== null) {
+                binaryValue = base65IndexValue.toString(2);
+                binaryString += addBase64Padding(binaryValue);
+            }
+        }
+        var byteArray = [];
+        var numberOfBytes = parseInt(binaryString.length / 8, 10);
+        for (i = 0; i < numberOfBytes; i++) {
+            var intValue = parseInt(binaryString.substring(i * 8, (i + 1) * 8), 2);
+            byteArray.push(intValue);
+        }
+        return byteArray;
+    };
+
+    var getBase64IndexValue = function (character) {
+        var asciiCode = character.charCodeAt(0);
+        var asciiOfA = 65;
+        var differenceBetweenZanda = 6;
+        if (asciiCode >= 65 && asciiCode <= 90) {           // between "A" and "Z" inclusive
+            return asciiCode - asciiOfA;
+        } else if (asciiCode >= 97 && asciiCode <= 122) {   // between 'a' and 'z' inclusive
+            return asciiCode - asciiOfA - differenceBetweenZanda;
+        } else if (asciiCode >= 48 && asciiCode <= 57) {    // between '0' and '9' inclusive
+            return asciiCode + 4;
+        } else if (character == "+") {
+            return 62;
+        } else if (character == "/") {
+            return 63;
+        } else {
+            return null;
+        }
+    };
+
+    var addBase64Padding = function (binaryString) {
+        while (binaryString.length < 6) {
+            binaryString = "0" + binaryString;
+        }
+        return binaryString;
+    };
+    // DATAJS INTERNAL START
+
+    datajs.activeXObject = activeXObject;
+    datajs.assigned = assigned;
+    datajs.contains = contains;
+    datajs.defined = defined;
+    datajs.delay = delay;
+    datajs.djsassert = djsassert;
+    datajs.extend = extend;
+    datajs.find = find;
+    datajs.getURIInfo = getURIInfo;
+    datajs.isArray = isArray;
+    datajs.isDate = isDate;
+    datajs.isObject = isObject;
+    datajs.normalizeURI = normalizeURI;
+    datajs.normalizeURICase = normalizeURICase;
+    datajs.parseInt10 = parseInt10;
+    datajs.renameProperty = renameProperty;
+    datajs.throwErrorCallback = throwErrorCallback;
+    datajs.trimString = trimString;
+    datajs.undefinedDefault = undefinedDefault;
+    datajs.decodeBase64 = decodeBase64;
+    datajs.convertByteArrayToHexString = convertByteArrayToHexString;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/src/xml.js b/JSLib/src/xml.js
new file mode 100644
index 0000000..895a4c7
--- /dev/null
+++ b/JSLib/src/xml.js
@@ -0,0 +1,824 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// xml.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs;
+
+    var activeXObject = datajs.activeXObject;
+    var djsassert = datajs.djsassert;
+    var extend = datajs.extend;
+    var isArray = datajs.isArray;
+    var isObject = datajs.isObject;
+    var normalizeURI = datajs.normalizeURI;
+
+    // CONTENT START
+
+    // URI prefixes to generate smaller code.
+    var http = "http://";
+    var w3org = http + "www.w3.org/";               // http://www.w3.org/
+
+    var xhtmlNS = w3org + "1999/xhtml";             // http://www.w3.org/1999/xhtml
+    var xmlnsNS = w3org + "2000/xmlns/";            // http://www.w3.org/2000/xmlns/
+    var xmlNS = w3org + "XML/1998/namespace";       // http://www.w3.org/XML/1998/namespace
+
+    var mozillaParserErroNS = http + "www.mozilla.org/newlayout/xml/parsererror.xml";
+
+    var hasLeadingOrTrailingWhitespace = function (text) {
+        /// <summary>Checks whether the specified string has leading or trailing spaces.</summary>
+        /// <param name="text" type="String">String to check.</param>
+        /// <returns type="Boolean">true if text has any leading or trailing whitespace; false otherwise.</returns>
+
+        var re = /(^\s)|(\s$)/;
+        return re.test(text);
+    };
+
+    var isWhitespace = function (text) {
+        /// <summary>Determines whether the specified text is empty or whitespace.</summary>
+        /// <param name="text" type="String">Value to inspect.</param>
+        /// <returns type="Boolean">true if the text value is empty or all whitespace; false otherwise.</returns>
+
+        var ws = /^\s*$/;
+        return text === null || ws.test(text);
+    };
+
+    var isWhitespacePreserveContext = function (domElement) {
+        /// <summary>Determines whether the specified element has xml:space='preserve' applied.</summary>
+        /// <param name="domElement">Element to inspect.</param>
+        /// <returns type="Boolean">Whether xml:space='preserve' is in effect.</returns>
+
+        while (domElement !== null && domElement.nodeType === 1) {
+            var val = xmlAttributeValue(domElement, "space", xmlNS);
+            if (val === "preserve") {
+                return true;
+            } else if (val === "default") {
+                break;
+            } else {
+                domElement = domElement.parentNode;
+            }
+        }
+
+        return false;
+    };
+
+    var isXmlNSDeclaration = function (domAttribute) {
+        /// <summary>Determines whether the attribute is a XML namespace declaration.</summary>
+        /// <param name="domAttribute">Element to inspect.</param>
+        /// <returns type="Boolean">
+        ///    True if the attribute is a namespace declaration (its name is 'xmlns' or starts with 'xmlns:'; false otherwise.
+        /// </returns>
+
+        var nodeName = domAttribute.nodeName;
+        return nodeName == "xmlns" || nodeName.indexOf("xmlns:") === 0;
+    };
+
+    var safeSetProperty = function (obj, name, value) {
+        /// <summary>Safely set as property in an object by invoking obj.setProperty.</summary>
+        /// <param name="obj">Object that exposes a setProperty method.</param>
+        /// <param name="name" type="String" mayBeNull="false">Property name.</param>
+        /// <param name="value">Property value.</param>
+
+        try {
+            obj.setProperty(name, value);
+        } catch (_) { }
+    };
+
+    var msXmlDom3 = function () {
+        /// <summary>Creates an configures new MSXML 3.0 ActiveX object.</summary>
+        /// <remakrs>
+        ///    This function throws any exception that occurs during the creation
+        ///    of the MSXML 3.0 ActiveX object.
+        /// <returns type="Object">New MSXML 3.0 ActiveX object.</returns>
+
+        var msxml3 = activeXObject("Msxml2.DOMDocument.3.0");
+        if (msxml3) {
+            safeSetProperty(msxml3, "ProhibitDTD", true);
+            safeSetProperty(msxml3, "MaxElementDepth", 256);
+            safeSetProperty(msxml3, "AllowDocumentFunction", false);
+            safeSetProperty(msxml3, "AllowXsltScript", false);
+        }
+        return msxml3;
+    };
+
+    var msXmlDom = function () {
+        /// <summary>Creates an configures new MSXML 6.0 or MSXML 3.0 ActiveX object.</summary>
+        /// <remakrs>
+        ///    This function will try to create a new MSXML 6.0 ActiveX object. If it fails then
+        ///    it will fallback to create a new MSXML 3.0 ActiveX object. Any exception that
+        ///    happens during the creation of the MSXML 6.0 will be handled by the function while
+        ///    the ones that happend during the creation of the MSXML 3.0 will be thrown.
+        /// <returns type="Object">New MSXML 3.0 ActiveX object.</returns>
+
+        try {
+            var msxml = activeXObject("Msxml2.DOMDocument.6.0");
+            if (msxml) {
+                msxml.async = true;
+            }
+            return msxml;
+        } catch (_) {
+            return msXmlDom3();
+        }
+    };
+
+    var msXmlParse = function (text) {
+        /// <summary>Parses an XML string using the MSXML DOM.</summary>
+        /// <remakrs>
+        ///    This function throws any exception that occurs during the creation
+        ///    of the MSXML ActiveX object.  It also will throw an exception
+        ///    in case of a parsing error.
+        /// <returns type="Object">New MSXML DOMDocument node representing the parsed XML string.</returns>
+
+        var dom = msXmlDom();
+        if (!dom) {
+            return null;
+        }
+
+        dom.loadXML(text);
+        var parseError = dom.parseError;
+        if (parseError.errorCode !== 0) {
+            xmlThrowParserError(parseError.reason, parseError.srcText, text);
+        }
+        return dom;
+    };
+
+    var xmlThrowParserError = function (exceptionOrReason, srcText, errorXmlText) {
+        /// <summary>Throws a new exception containing XML parsing error information.</summary>
+        /// <param name="exceptionOrReason">
+        ///    String indicatin the reason of the parsing failure or
+        ///    Object detailing the parsing error.
+        /// </param>
+        /// <param name="srcText" type="String">
+        ///    String indicating the part of the XML string that caused the parsing error.
+        /// </param>
+        /// <param name="errorXmlText" type="String">XML string for wich the parsing failed.</param>
+
+        if (typeof exceptionOrReason === "string") {
+            exceptionOrReason = { message: exceptionOrReason };
+        }
+        throw extend(exceptionOrReason, { srcText: srcText || "", errorXmlText: errorXmlText || "" });
+    };
+
+    var xmlParse = function (text) {
+        /// <summary>Returns an XML DOM document from the specified text.</summary>
+        /// <param name="text" type="String">Document text.</param>
+        /// <returns>XML DOM document.</returns>
+        /// <remarks>This function will throw an exception in case of a parse error.</remarks>
+
+        var domParser = window.DOMParser && new window.DOMParser();
+        var dom;
+
+        if (!domParser) {
+            dom = msXmlParse(text);
+            if (!dom) {
+                xmlThrowParserError("XML DOM parser not supported");
+            }
+            return dom;
+        }
+
+        try {
+            dom = domParser.parseFromString(text, "text/xml");
+        } catch (e) {
+            xmlThrowParserError(e, "", text);
+        }
+
+        var element = dom.documentElement;
+        var nsURI = element.namespaceURI;
+        var localName = xmlLocalName(element);
+
+        // Firefox reports errors by returing the DOM for an xml document describing the problem.
+        if (localName === "parsererror" && nsURI === mozillaParserErroNS) {
+            var srcTextElement = xmlFirstChildElement(element, mozillaParserErroNS, "sourcetext");
+            var srcText = srcTextElement ? xmlNodeValue(srcTextElement) : "";
+            xmlThrowParserError(xmlInnerText(element) || "", srcText, text);
+        }
+
+        // Chrome (and maybe other webkit based browsers) report errors by injecting a header with an error message.
+        // The error may be localized, so instead we simply check for a header as the
+        // top element or descendant child of the document.
+        if (localName === "h3" && nsURI === xhtmlNS || xmlFirstDescendantElement(element, xhtmlNS, "h3")) {
+            var reason = "";
+            var siblings = [];
+            var cursor = element.firstChild;
+            while (cursor) {
+                if (cursor.nodeType === 1) {
+                    reason += xmlInnerText(cursor) || "";
+                }
+                siblings.push(cursor.nextSibling);
+                cursor = cursor.firstChild || siblings.shift();
+            }
+            reason += xmlInnerText(element) || "";
+            xmlThrowParserError(reason, "", text);
+        }
+
+        return dom;
+    };
+
+    var xmlQualifiedName = function (prefix, name) {
+        /// <summary>Builds a XML qualified name string in the form of "prefix:name".</summary>
+        /// <param name="prefix" type="String" maybeNull="true">Prefix string.</param>
+        /// <param name="name" type="String">Name string to qualify with the prefix.</param>
+        /// <returns type="String">Qualified name.</returns>
+
+        return prefix ? prefix + ":" + name : name;
+    };
+
+    var xmlAppendText = function (domNode, textNode) {
+        /// <summary>Appends a text node into the specified DOM element node.</summary>
+        /// <param name="domNode">DOM node for the element.</param>
+        /// <param name="text" type="String" mayBeNull="false">Text to append as a child of element.</param>
+        if (hasLeadingOrTrailingWhitespace(textNode.data)) {
+            var attr = xmlAttributeNode(domNode, xmlNS, "space");
+            if (!attr) {
+                attr = xmlNewAttribute(domNode.ownerDocument, xmlNS, xmlQualifiedName("xml", "space"));
+                xmlAppendChild(domNode, attr);
+            }
+            attr.value = "preserve";
+        }
+        domNode.appendChild(textNode);
+        return domNode;
+    };
+
+    var xmlAttributes = function (element, onAttributeCallback) {
+        /// <summary>Iterates through the XML element's attributes and invokes the callback function for each one.</summary>
+        /// <param name="element">Wrapped element to iterate over.</param>
+        /// <param name="onAttributeCallback" type="Function">Callback function to invoke with wrapped attribute nodes.</param>
+
+        var attributes = element.attributes;
+        var i, len;
+        for (i = 0, len = attributes.length; i < len; i++) {
+            onAttributeCallback(attributes.item(i));
+        }
+    };
+
+    var xmlAttributeValue = function (domNode, localName, nsURI) {
+        /// <summary>Returns the value of a DOM element's attribute.</summary>
+        /// <param name="domNode">DOM node for the owning element.</param>
+        /// <param name="localName" type="String">Local name of the attribute.</param>
+        /// <param name="nsURI" type="String">Namespace URI of the attribute.</param>
+        /// <returns type="String" maybeNull="true">The attribute value, null if not found.</returns>
+
+        var attribute = xmlAttributeNode(domNode, localName, nsURI);
+        return attribute ? xmlNodeValue(attribute) : null;
+    };
+
+    var xmlAttributeNode = function (domNode, localName, nsURI) {
+        /// <summary>Gets an attribute node from a DOM element.</summary>
+        /// <param name="domNode">DOM node for the owning element.</param>
+        /// <param name="localName" type="String">Local name of the attribute.</param>
+        /// <param name="nsURI" type="String">Namespace URI of the attribute.</param>
+        /// <returns>The attribute node, null if not found.</returns>
+
+        var attributes = domNode.attributes;
+        if (attributes.getNamedItemNS) {
+            return attributes.getNamedItemNS(nsURI || null, localName);
+        }
+
+        return attributes.getQualifiedItem(localName, nsURI) || null;
+    };
+
+    var xmlBaseURI = function (domNode, baseURI) {
+        /// <summary>Gets the value of the xml:base attribute on the specified element.</summary>
+        /// <param name="domNode">Element to get xml:base attribute value from.</param>
+        /// <param name="baseURI" mayBeNull="true" optional="true">Base URI used to normalize the value of the xml:base attribute.</param>
+        /// <returns type="String">Value of the xml:base attribute if found; the baseURI or null otherwise.</returns>
+
+        var base = xmlAttributeNode(domNode, "base", xmlNS);
+        return (base ? normalizeURI(base.value, baseURI) : baseURI) || null;
+    };
+
+
+    var xmlChildElements = function (domNode, onElementCallback) {
+        /// <summary>Iterates through the XML element's child DOM elements and invokes the callback function for each one.</summary>
+        /// <param name="element">DOM Node containing the DOM elements to iterate over.</param>
+        /// <param name="onElementCallback" type="Function">Callback function to invoke for each child DOM element.</param>
+
+        xmlTraverse(domNode, /*recursive*/false, function (child) {
+            if (child.nodeType === 1) {
+                onElementCallback(child);
+            }
+            // continue traversing.
+            return true;
+        });
+    };
+
+    var xmlFindElementByPath = function (root, namespaceURI, path) {
+        /// <summary>Gets the descendant element under root that corresponds to the specified path and namespace URI.</summary>
+        /// <param name="root">DOM element node from which to get the descendant element.</param>
+        /// <param name="namespaceURI" type="String">The namespace URI of the element to match.</param>
+        /// <param name="path" type="String">Path to the desired descendant element.</param>
+        /// <returns>The element specified by path and namespace URI.</returns>
+        /// <remarks>
+        ///     All the elements in the path are matched against namespaceURI.
+        ///     The function will stop searching on the first element that doesn't match the namespace and the path.
+        /// </remarks>
+
+        var parts = path.split("/");
+        var i, len;
+        for (i = 0, len = parts.length; i < len; i++) {
+            root = root && xmlFirstChildElement(root, namespaceURI, parts[i]);
+        }
+        return root || null;
+    };
+
+    var xmlFindNodeByPath = function (root, namespaceURI, path) {
+        /// <summary>Gets the DOM element or DOM attribute node under root that corresponds to the specified path and namespace URI.</summary>
+        /// <param name="root">DOM element node from which to get the descendant node.</param>
+        /// <param name="namespaceURI" type="String">The namespace URI of the node to match.</param>
+        /// <param name="path" type="String">Path to the desired descendant node.</param>
+        /// <returns>The node specified by path and namespace URI.</returns>
+        /// <remarks>
+        ///     This function will traverse the path and match each node associated to a path segement against the namespace URI.
+        ///     The traversal stops when the whole path has been exahusted or a node that doesn't belogong the specified namespace is encountered.
+        ///
+        ///     The last segment of the path may be decorated with a starting @ character to indicate that the desired node is a DOM attribute.
+        /// </remarks>
+
+        var lastSegmentStart = path.lastIndexOf("/");
+        var nodePath = path.substring(lastSegmentStart + 1);
+        var parentPath = path.substring(0, lastSegmentStart);
+
+        var node = parentPath ? xmlFindElementByPath(root, namespaceURI, parentPath) : root;
+        if (node) {
+            if (nodePath.charAt(0) === "@") {
+                return xmlAttributeNode(node, nodePath.substring(1), namespaceURI);
+            }
+            return xmlFirstChildElement(node, namespaceURI, nodePath);
+        }
+        return null;
+    };
+
+    var xmlFirstChildElement = function (domNode, namespaceURI, localName) {
+        /// <summary>Returns the first child DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary>
+        /// <param name="domNode">DOM node from which the child DOM element is going to be retrieved.</param>
+        /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param>
+        /// <param name="localName" type="String" optional="true">Name of the element to match.</param>
+        /// <returns>The node's first child DOM element that matches the specified namespace URI and local name; null otherwise.</returns>
+
+        return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/false);
+    };
+
+    var xmlFirstDescendantElement = function (domNode, namespaceURI, localName) {
+        /// <summary>Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary>
+        /// <param name="domNode">DOM node from which the descendant DOM element is going to be retrieved.</param>
+        /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param>
+        /// <param name="localName" type="String" optional="true">Name of the element to match.</param>
+        /// <returns>The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise.</returns>
+
+        if (domNode.getElementsByTagNameNS) {
+            var result = domNode.getElementsByTagNameNS(namespaceURI, localName);
+            return result.length > 0 ? result[0] : null;
+        }
+        return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/true);
+    };
+
+    var xmlFirstElementMaybeRecursive = function (domNode, namespaceURI, localName, recursive) {
+        /// <summary>Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary>
+        /// <param name="domNode">DOM node from which the descendant DOM element is going to be retrieved.</param>
+        /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param>
+        /// <param name="localName" type="String" optional="true">Name of the element to match.</param>
+        /// <param name="recursive" type="Boolean">
+        ///     True if the search should include all the descendants of the DOM node.
+        ///     False if the search should be scoped only to the direct children of the DOM node.
+        /// </param>
+        /// <returns>The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise.</returns>
+
+        var firstElement = null;
+        xmlTraverse(domNode, recursive, function (child) {
+            if (child.nodeType === 1) {
+                var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(child) === namespaceURI;
+                var isExpectedNodeName = !localName || xmlLocalName(child) === localName;
+
+                if (isExpectedNamespace && isExpectedNodeName) {
+                    firstElement = child;
+                }
+            }
+            return firstElement === null;
+        });
+        return firstElement;
+    };
+
+    var xmlInnerText = function (xmlElement) {
+        /// <summary>Gets the concatenated value of all immediate child text and CDATA nodes for the specified element.</summary>
+        /// <param name="domElement">Element to get values for.</param>
+        /// <returns type="String">Text for all direct children.</returns>
+
+        var result = null;
+        var root = (xmlElement.nodeType === 9 && xmlElement.documentElement) ? xmlElement.documentElement : xmlElement;
+        var whitespaceAlreadyRemoved = root.ownerDocument.preserveWhiteSpace === false;
+        var whitespacePreserveContext;
+
+        xmlTraverse(root, false, function (child) {
+            if (child.nodeType === 3 || child.nodeType === 4) {
+                // isElementContentWhitespace indicates that this is 'ignorable whitespace',
+                // but it's not defined by all browsers, and does not honor xml:space='preserve'
+                // in some implementations.
+                //
+                // If we can't tell either way, we walk up the tree to figure out whether
+                // xml:space is set to preserve; otherwise we discard pure-whitespace.
+                //
+                // For example <a>  <b>1</b></a>. The space between <a> and <b> is usually 'ignorable'.
+                var text = xmlNodeValue(child);
+                var shouldInclude = whitespaceAlreadyRemoved || !isWhitespace(text);
+                if (!shouldInclude) {
+                    // Walk up the tree to figure out whether we are in xml:space='preserve' context
+                    // for the cursor (needs to happen only once).
+                    if (whitespacePreserveContext === undefined) {
+                        whitespacePreserveContext = isWhitespacePreserveContext(root);
+                    }
+
+                    shouldInclude = whitespacePreserveContext;
+                }
+
+                if (shouldInclude) {
+                    if (!result) {
+                        result = text;
+                    } else {
+                        result += text;
+                    }
+                }
+            }
+            // Continue traversing?
+            return true;
+        });
+        return result;
+    };
+
+    var xmlLocalName = function (domNode) {
+        /// <summary>Returns the localName of a XML node.</summary>
+        /// <param name="domNode">DOM node to get the value from.</param>
+        /// <returns type="String">localName of domNode.</returns>
+
+        return domNode.localName || domNode.baseName;
+    };
+
+    var xmlNamespaceURI = function (domNode) {
+        /// <summary>Returns the namespace URI of a XML node.</summary>
+        /// <param name="node">DOM node to get the value from.</param>
+        /// <returns type="String">Namespace URI of domNode.</returns>
+
+        return domNode.namespaceURI || null;
+    };
+
+    var xmlNodeValue = function (domNode) {
+        /// <summary>Returns the value or the inner text of a XML node.</summary>
+        /// <param name="node">DOM node to get the value from.</param>
+        /// <returns>Value of the domNode or the inner text if domNode represents a DOM element node.</returns>
+        
+        if (domNode.nodeType === 1) {
+            return xmlInnerText(domNode);
+        }
+        return domNode.nodeValue;
+    };
+
+    var xmlTraverse = function (domNode, recursive, onChildCallback) {
+        /// <summary>Walks through the descendants of the domNode and invokes a callback for each node.</summary>
+        /// <param name="domNode">DOM node whose descendants are going to be traversed.</param>
+        /// <param name="recursive" type="Boolean">
+        ///    True if the traversal should include all the descenants of the DOM node.
+        ///    False if the traversal should be scoped only to the direct children of the DOM node.
+        /// </param>
+        /// <returns type="String">Namespace URI of node.</returns>
+
+        var subtrees = [];
+        var child = domNode.firstChild;
+        var proceed = true;
+        while (child && proceed) {
+            proceed = onChildCallback(child);
+            if (proceed) {
+                if (recursive && child.firstChild) {
+                    subtrees.push(child.firstChild);
+                }
+                child = child.nextSibling || subtrees.shift();
+            }
+        }
+    };
+
+    var xmlSiblingElement = function (domNode, namespaceURI, localName) {
+        /// <summary>Returns the next sibling DOM element of the specified DOM node.</summary>
+        /// <param name="domNode">DOM node from which the next sibling is going to be retrieved.</param>
+        /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param>
+        /// <param name="localName" type="String" optional="true">Name of the element to match.</param>
+        /// <returns>The node's next sibling DOM element, null if there is none.</returns>
+
+        var sibling = domNode.nextSibling;
+        while (sibling) {
+            if (sibling.nodeType === 1) {
+                var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(sibling) === namespaceURI;
+                var isExpectedNodeName = !localName || xmlLocalName(sibling) === localName;
+
+                if (isExpectedNamespace && isExpectedNodeName) {
+                    return sibling;
+                }
+            }
+            sibling = sibling.nextSibling;
+        }
+        return null;
+    };
+
+    var xmlDom = function () {
+        /// <summary>Creates a new empty DOM document node.</summary>
+        /// <returns>New DOM document node.</returns>
+        /// <remarks>
+        ///    This function will first try to create a native DOM document using
+        ///    the browsers createDocument function.  If the browser doesn't
+        ///    support this but supports ActiveXObject, then an attempt to create
+        ///    an MSXML 6.0 DOM will be made. If this attempt fails too, then an attempt
+        ///    for creating an MXSML 3.0 DOM will be made.  If this last attemp fails or
+        ///    the browser doesn't support ActiveXObject then an exception will be thrown.
+        /// </remarks>
+
+        var implementation = window.document.implementation;
+        return (implementation && implementation.createDocument) ?
+           implementation.createDocument(null, null, null) :
+           msXmlDom();
+    };
+
+    var xmlAppendChildren = function (parent, children) {
+        /// <summary>Appends a collection of child nodes or string values to a parent DOM node.</summary>
+        /// <param name="parent">DOM node to which the children will be appended.</param>
+        /// <param name="children" type="Array">Array containing DOM nodes or string values that will be appended to the parent.</param>
+        /// <returns>The parent with the appended children or string values.</returns>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended to the parent.
+        /// </remarks>
+
+        if (!isArray(children)) {
+            return xmlAppendChild(parent, children);
+        }
+
+        var i, len;
+        for (i = 0, len = children.length; i < len; i++) {
+            children[i] && xmlAppendChild(parent, children[i]);
+        }
+        return parent;
+    };
+
+    var xmlAppendChild = function (parent, child) {
+        /// <summary>Appends a child node or a string value to a parent DOM node.</summary>
+        /// <param name="parent">DOM node to which the child will be appended.</param>
+        /// <param name="child">Child DOM node or string value to append to the parent.</param>
+        /// <returns>The parent with the appended child or string value.</returns>
+        /// <remarks>
+        ///    If child is a string value, then a new DOM text node is going to be created
+        ///    for it and then appended to the parent.
+        /// </remarks>
+
+        djsassert(parent !== child, "xmlAppendChild() - parent and child are one and the same!");
+        if (child) {
+            if (typeof child === "string") {
+                return xmlAppendText(parent, xmlNewText(parent.ownerDocument, child));
+            }
+            if (child.nodeType === 2) {
+                parent.setAttributeNodeNS ? parent.setAttributeNodeNS(child) : parent.setAttributeNode(child);
+            } else {
+                parent.appendChild(child);
+            }
+        }
+        return parent;
+    };
+
+    var xmlNewAttribute = function (dom, namespaceURI, qualifiedName, value) {
+        /// <summary>Creates a new DOM attribute node.</summary>
+        /// <param name="dom">DOM document used to create the attribute.</param>
+        /// <param name="prefix" type="String">Namespace prefix.</param>
+        /// <param name="namespaceURI" type="String">Namespace URI.</param>
+        /// <returns>DOM attribute node for the namespace declaration.</returns>
+
+        var attribute =
+            dom.createAttributeNS && dom.createAttributeNS(namespaceURI, qualifiedName) ||
+            dom.createNode(2, qualifiedName, namespaceURI || undefined);
+
+        attribute.value = value || "";
+        return attribute;
+    };
+
+    var xmlNewElement = function (dom, nampespaceURI, qualifiedName, children) {
+        /// <summary>Creates a new DOM element node.</summary>
+        /// <param name="dom">DOM document used to create the DOM element.</param>
+        /// <param name="namespaceURI" type="String">Namespace URI of the new DOM element.</param>
+        /// <param name="qualifiedName" type="String">Qualified name in the form of "prefix:name" of the new DOM element.</param>
+        /// <param name="children" type="Array" optional="true">
+        ///     Collection of child DOM nodes or string values that are going to be appended to the new DOM element.
+        /// </param>
+        /// <returns>New DOM element.</returns>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended to the new DOM element.
+        /// </remarks>
+
+        var element =
+            dom.createElementNS && dom.createElementNS(nampespaceURI, qualifiedName) ||
+            dom.createNode(1, qualifiedName, nampespaceURI || undefined);
+
+        return xmlAppendChildren(element, children || []);
+    };
+
+    var xmlNewNSDeclaration = function (dom, namespaceURI, prefix) {
+        /// <summary>Creates a namespace declaration attribute.</summary>
+        /// <param name="dom">DOM document used to create the attribute.</param>
+        /// <param name="namespaceURI" type="String">Namespace URI.</param>
+        /// <param name="prefix" type="String">Namespace prefix.</param>
+        /// <returns>DOM attribute node for the namespace declaration.</returns>
+
+        return xmlNewAttribute(dom, xmlnsNS, xmlQualifiedName("xmlns", prefix), namespaceURI);
+    };
+
+    var xmlNewFragment = function (dom, text) {
+        /// <summary>Creates a new DOM document fragment node for the specified xml text.</summary>
+        /// <param name="dom">DOM document from which the fragment node is going to be created.</param>
+        /// <param name="text" type="String" mayBeNull="false">XML text to be represented by the XmlFragment.</param>
+        /// <returns>New DOM document fragment object.</returns>
+
+        var value = "<c>" + text + "</c>";
+        var tempDom = xmlParse(value);
+        var tempRoot = tempDom.documentElement;
+        var imported = ("importNode" in dom) ? dom.importNode(tempRoot, true) : tempRoot;
+        var fragment = dom.createDocumentFragment();
+
+        var importedChild = imported.firstChild;
+        while (importedChild) {
+            fragment.appendChild(importedChild);
+            importedChild = importedChild.nextSibling;
+        }
+        return fragment;
+    };
+
+    var xmlNewText = function (dom, text) {
+        /// <summary>Creates new DOM text node.</summary>
+        /// <param name="dom">DOM document used to create the text node.</param>
+        /// <param name="text" type="String">Text value for the DOM text node.</param>
+        /// <returns>DOM text node.</returns>
+
+        return dom.createTextNode(text);
+    };
+
+    var xmlNewNodeByPath = function (dom, root, namespaceURI, prefix, path) {
+        /// <summary>Creates a new DOM element or DOM attribute node as specified by path and appends it to the DOM tree pointed by root.</summary>
+        /// <param name="dom">DOM document used to create the new node.</param>
+        /// <param name="root">DOM element node used as root of the subtree on which the new nodes are going to be created.</param>
+        /// <param name="namespaceURI" type="String">Namespace URI of the new DOM element or attribute.</param>
+        /// <param name="namespacePrefix" type="String">Prefix used to qualify the name of the new DOM element or attribute.</param>
+        /// <param name="Path" type="String">Path string describing the location of the new DOM element or attribute from the root element.</param>
+        /// <returns>DOM element or attribute node for the last segment of the path.</returns>
+        /// <remarks>
+        ///     This function will traverse the path and will create a new DOM element with the specified namespace URI and prefix
+        ///     for each segment that doesn't have a matching element under root.
+        ///
+        ///     The last segment of the path may be decorated with a starting @ character. In this case a new DOM attribute node
+        ///     will be created.
+        /// </remarks>
+
+        var name = "";
+        var parts = path.split("/");
+        var xmlFindNode = xmlFirstChildElement;
+        var xmlNewNode = xmlNewElement;
+        var xmlNode = root;
+
+        var i, len;
+        for (i = 0, len = parts.length; i < len; i++) {
+            name = parts[i];
+            if (name.charAt(0) === "@") {
+                name = name.substring(1);
+                xmlFindNode = xmlAttributeNode;
+                xmlNewNode = xmlNewAttribute;
+            }
+
+            var childNode = xmlFindNode(xmlNode, namespaceURI, name);
+            if (!childNode) {
+                childNode = xmlNewNode(dom, namespaceURI, xmlQualifiedName(prefix, name));
+                xmlAppendChild(xmlNode, childNode);
+            }
+            xmlNode = childNode;
+        }
+        return xmlNode;
+    };
+
+    var xmlSerialize = function (domNode) {
+        /// <summary>
+        /// Returns the text representation of the document to which the specified node belongs.
+        /// </summary>
+        /// <param name="root">Wrapped element in the document to serialize.</param>
+        /// <returns type="String">Serialized document.</returns>
+
+        var xmlSerializer = window.XMLSerializer;
+        if (xmlSerializer) {
+            var serializer = new xmlSerializer();
+            return serializer.serializeToString(domNode);
+        }
+
+        if (domNode.xml) {
+            return domNode.xml;
+        }
+
+        throw { message: "XML serialization unsupported" };
+    };
+
+    var xmlSerializeDescendants = function (domNode) {
+        /// <summary>Returns the XML representation of the all the descendants of the node.</summary>
+        /// <param name="domNode" optional="false" mayBeNull="false">Node to serialize.</param>
+        /// <returns type="String">The XML representation of all the descendants of the node.</returns>
+
+        var children = domNode.childNodes;
+        var i, len = children.length;
+        if (len === 0) {
+            return "";
+        }
+
+        // Some implementations of the XMLSerializer don't deal very well with fragments that
+        // don't have a DOMElement as their first child. The work around is to wrap all the
+        // nodes in a dummy root node named "c", serialize it and then just extract the text between
+        // the <c> and the </c> substrings.
+
+        var dom = domNode.ownerDocument;
+        var fragment = dom.createDocumentFragment();
+        var fragmentRoot = dom.createElement("c");
+
+        fragment.appendChild(fragmentRoot);
+        // Move the children to the fragment tree.
+        for (i = 0; i < len; i++) {
+            fragmentRoot.appendChild(children[i]);
+        }
+
+        var xml = xmlSerialize(fragment);
+        xml = xml.substr(3, xml.length - 7);
+
+        // Move the children back to the original dom tree.
+        for (i = 0; i < len; i++) {
+            domNode.appendChild(fragmentRoot.childNodes[i]);
+        }
+
+        return xml;
+    };
+
+    var xmlSerializeNode = function (domNode) {
+        /// <summary>Returns the XML representation of the node and all its descendants.</summary>
+        /// <param name="domNode" optional="false" mayBeNull="false">Node to serialize.</param>
+        /// <returns type="String">The XML representation of the node and all its descendants.</returns>
+
+        var xml = domNode.xml;
+        if (xml !== undefined) {
+            return xml;
+        }
+
+        if (window.XMLSerializer) {
+            var serializer = new window.XMLSerializer();
+            return serializer.serializeToString(domNode);
+        }
+
+        throw { message: "XML serialization unsupported" };
+    };
+
+    // DATAJS INTERNAL START
+
+    datajs.http = http;
+    datajs.w3org = w3org;
+    datajs.xmlNS = xmlNS;
+    datajs.xmlnsNS = xmlnsNS;
+
+    datajs.hasLeadingOrTrailingWhitespace = hasLeadingOrTrailingWhitespace;
+    datajs.isXmlNSDeclaration = isXmlNSDeclaration;
+    datajs.xmlAppendChild = xmlAppendChild;
+    datajs.xmlAppendChildren = xmlAppendChildren;
+    datajs.xmlAttributeNode = xmlAttributeNode;
+    datajs.xmlAttributes = xmlAttributes;
+    datajs.xmlAttributeValue = xmlAttributeValue;
+    datajs.xmlBaseURI = xmlBaseURI;
+    datajs.xmlChildElements = xmlChildElements;
+    datajs.xmlFindElementByPath = xmlFindElementByPath;
+    datajs.xmlFindNodeByPath = xmlFindNodeByPath;
+    datajs.xmlFirstChildElement = xmlFirstChildElement;
+    datajs.xmlFirstDescendantElement = xmlFirstDescendantElement;
+    datajs.xmlInnerText = xmlInnerText;
+    datajs.xmlLocalName = xmlLocalName;
+    datajs.xmlNamespaceURI = xmlNamespaceURI;
+    datajs.xmlNodeValue = xmlNodeValue;
+    datajs.xmlDom = xmlDom;
+    datajs.xmlNewAttribute = xmlNewAttribute;
+    datajs.xmlNewElement = xmlNewElement;
+    datajs.xmlNewFragment = xmlNewFragment;
+    datajs.xmlNewNodeByPath = xmlNewNodeByPath;
+    datajs.xmlNewNSDeclaration = xmlNewNSDeclaration;
+    datajs.xmlNewText = xmlNewText;
+    datajs.xmlParse = xmlParse;
+    datajs.xmlQualifiedName = xmlQualifiedName;
+    datajs.xmlSerialize = xmlSerialize;
+    datajs.xmlSerializeDescendants = xmlSerializeDescendants;
+    datajs.xmlSiblingElement = xmlSiblingElement;
+
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file
diff --git a/JSLib/tests/code/ReflectionDataContext.cs b/JSLib/tests/code/ReflectionDataContext.cs
new file mode 100644
index 0000000..762b56f
--- /dev/null
+++ b/JSLib/tests/code/ReflectionDataContext.cs
@@ -0,0 +1,742 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections;
+    using System.Collections.Generic;
+    using System.Collections.ObjectModel;
+    using System.Data.Services;
+    using System.Data.Services.Common;
+    using System.Globalization;
+    using System.Linq;
+    using System.Reflection;
+
+    /// <summary>
+    /// Provides a reflection-based, updatable data context.
+    /// </summary>
+    public abstract class ReflectionDataContext
+    {
+        // Fields
+        private List<object> deletedObjects = new List<object>();
+        private List<Action> pendingChanges;
+        private static Dictionary<Type, Dictionary<string, IList>> resourceSetsByContextTypeStorage = new Dictionary<Type, Dictionary<string, IList>>();
+
+        // Methods
+        protected ReflectionDataContext()
+        {
+            this.MetadataHelper = new ReflectionMetadataHelper(this);
+            this.pendingChanges = new List<Action>();
+            if (!resourceSetsByContextTypeStorage.ContainsKey(base.GetType()))
+            {
+                resourceSetsByContextTypeStorage.Add(base.GetType(), new Dictionary<string, IList>());
+                foreach (string resourceSetName in this.MetadataHelper.GetResourceSetNames())
+                {
+                    Type resourceType = this.MetadataHelper.GetResourceTypeOfSet(resourceSetName);
+                    IList listOfTInstance = Activator.CreateInstance(typeof(List<>).MakeGenericType(new Type[] { resourceType })) as IList;
+                    this.ResourceSetsStorage.Add(resourceSetName, listOfTInstance);
+                }
+            }
+            this.EnsureDataIsInitialized();
+        }
+
+        public virtual void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName");
+            ExceptionUtilities.CheckArgumentNotNull(resourceToBeAdded, "resourceToBeAdded");
+            UpdatableToken targetToken = UpdatableToken.AssertIsToken(targetResource, "targetResource");
+            targetResource = targetToken.Resource;
+            resourceToBeAdded = UpdatableToken.AssertIsTokenAndResolve(resourceToBeAdded, "resourceToBeAdded");
+            IList list = this.GetValue(targetToken, propertyName) as IList;
+            ExceptionUtilities.CheckObjectNotNull(list, "Property '{0}' on type '{1}' was not a list", new object[] { propertyName, targetResource.GetType().Name });
+            this.pendingChanges.Add(delegate {
+                list.Add(resourceToBeAdded);
+            });
+        }
+
+        public virtual void ClearChanges()
+        {
+            this.pendingChanges.Clear();
+        }
+
+        public void ClearData()
+        {
+            this.ResourceSetsStorage.Clear();
+        }
+
+        private static bool CompareETagValues(Dictionary<string, object> resourceCookieValues, IEnumerable<KeyValuePair<string, object>> concurrencyValues)
+        {
+            if (concurrencyValues.Count<KeyValuePair<string, object>>() != resourceCookieValues.Count)
+            {
+                return false;
+            }
+            foreach (KeyValuePair<string, object> keyValuePair in concurrencyValues)
+            {
+                if (!resourceCookieValues.ContainsKey(keyValuePair.Key))
+                {
+                    return false;
+                }
+                if (keyValuePair.Value == null)
+                {
+                    return (resourceCookieValues[keyValuePair.Key] == null);
+                }
+                if (!keyValuePair.Value.Equals(resourceCookieValues[keyValuePair.Key]))
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public virtual object CreateResource(string containerName, string fullTypeName)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(fullTypeName, "fullTypeName");
+            UpdatableToken token = this.InstantiateResourceType(fullTypeName);
+            if (containerName != null)
+            {
+                this.pendingChanges.Add(delegate {
+                    this.GetResourceSetEntities(containerName).Add(token.Resource);
+                });
+            }
+            return token;
+        }
+
+        private void DeleteAllReferences(object targetResource)
+        {
+            foreach (string currentSetName in this.MetadataHelper.GetResourceSetNames())
+            {
+                Type currentEntityType = this.MetadataHelper.GetResourceTypeOfSet(currentSetName);
+                IList entitySetList = this.GetResourceSetEntities(currentSetName);
+                foreach (NavigationPropertyInfo navigationProperty in this.MetadataHelper.GetNavigationProperties(GetResourceTypeFullName(currentEntityType)))
+                {
+                    if (navigationProperty.CollectionElementType != null)
+                    {
+                        foreach (object currentEntityInstance in entitySetList)
+                        {
+                            this.RemoveResourceFromCollectionOnTargetResourceMatch(targetResource, navigationProperty, currentEntityInstance);
+                        }
+                    }
+                    else
+                    {
+                        ExceptionUtilities.CheckObjectNotNull(navigationProperty.PropertyInfo, "Invalid navigation property info", new object[0]);
+                        foreach (object currentEntityInstance in entitySetList)
+                        {
+                            this.SetEntityReferenceToNullOnTargetResourceMatch(targetResource, navigationProperty, currentEntityInstance);
+                        }
+                    }
+                }
+            }
+        }
+
+        public virtual void DeleteResource(object targetResource)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            targetResource = UpdatableToken.AssertIsTokenAndResolve(targetResource, "targetResource");
+            string resourceSetName = this.GetResourceSetOfTargetResource(targetResource);
+            ExceptionUtilities.CheckObjectNotNull(resourceSetName, "Unable to find set of the resource to delete", new object[0]);
+            this.deletedObjects.Add(targetResource);
+            IList resourceSetList = this.GetResourceSetEntities(resourceSetName);
+            this.DeleteAllReferences(targetResource);
+            this.pendingChanges.Add(delegate {
+                resourceSetList.Remove(targetResource);
+            });
+        }
+
+        protected abstract void EnsureDataIsInitialized();
+
+        protected virtual Type GetCollectionPropertyType(string fullTypeName, string propertyName)
+        {
+            Type type = this.MetadataHelper.FindClrTypeByFullName(fullTypeName);
+            Type collectionType = null;
+            if (type != null)
+            {
+                PropertyInfo property = type.GetProperty(propertyName);
+                if (property != null)
+                {
+                    collectionType = property.PropertyType;
+                }
+            }
+            return collectionType;
+        }
+
+        private Dictionary<string, object> GetConcurrencyValues(object targetResource)
+        {
+            Dictionary<string, object> etagValues = new Dictionary<string, object>();
+            foreach (string etagProperty in this.MetadataHelper.GetETagPropertiesOfType(GetResourceTypeFullName(targetResource.GetType())))
+            {
+                etagValues.Add(etagProperty, targetResource.GetType().GetProperty(etagProperty).GetValue(targetResource, null));
+            }
+            return etagValues;
+        }
+
+        public virtual object GetResource(IQueryable query, string fullTypeName)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(query, "query");
+            object resource = null;
+            foreach (object r in query)
+            {
+                ExceptionUtilities.Assert(resource == null, "Invalid Uri specified. The query '{0}' must refer to a single resource", new object[] { query.ToString() });
+                resource = r;
+            }
+            if (resource == null)
+            {
+                return null;
+            }
+            if (fullTypeName != null)
+            {
+                this.ValidateResourceType(resource, fullTypeName);
+            }
+            return new UpdatableToken(resource);
+        }
+
+        public IList<T> GetResourceSetEntities<T>(string resourceSetName)
+        {
+            return (IList<T>) this.GetResourceSetEntities(resourceSetName);
+        }
+
+        internal IList GetResourceSetEntities(string resourceSetName)
+        {
+            IList entities;
+            if (!this.ResourceSetsStorage.TryGetValue(resourceSetName, out entities))
+            {
+                Type elementType = this.MetadataHelper.GetResourceTypeOfSet(resourceSetName);
+                entities = (IList) Activator.CreateInstance(typeof(List<>).MakeGenericType(new Type[] { elementType }));
+                this.ResourceSetsStorage[resourceSetName] = entities;
+            }
+            return entities;
+        }
+
+        private string GetResourceSetOfTargetResource(object targetResource)
+        {
+            foreach (string currentResourceSetName in this.MetadataHelper.GetResourceSetNames())
+            {
+                if (this.GetResourceSetEntities(currentResourceSetName).Contains(targetResource))
+                {
+                    return currentResourceSetName;
+                }
+            }
+            return null;
+        }
+
+        public static string GetResourceTypeFullName(Type type)
+        {
+            return type.FullName.Replace('+', '_');
+        }
+
+        public virtual object GetValue(object targetResource, string propertyName)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName");
+            UpdatableToken token = UpdatableToken.AssertIsToken(targetResource, "targetResource");
+            if (token.PendingPropertyUpdates.ContainsKey(propertyName))
+            {
+                return token.PendingPropertyUpdates[propertyName];
+            }
+            targetResource = token.Resource;
+            PropertyInfo pi = targetResource.GetType().GetProperty(propertyName);
+            ExceptionUtilities.CheckObjectNotNull(pi, "Cannot find the property '{0}' on type '{1}'", new object[] { propertyName, targetResource.GetType().Name });
+            object value = pi.GetValue(targetResource, null);
+            if ((value != null) && (pi.PropertyType.Assembly == base.GetType().Assembly))
+            {
+                ExceptionUtilities.Assert(!this.MetadataHelper.IsTypeAnEntityType(pi.PropertyType), "GetValue should never be called for reference properties. Type was '{0}', property was '{1}'", new object[] { pi.PropertyType.FullName, propertyName });
+                value = new UpdatableToken(value);
+            }
+            return value;
+        }
+
+        private UpdatableToken InstantiateResourceType(string fullTypeName)
+        {
+            Type t = this.MetadataHelper.FindClrTypeByFullName(fullTypeName);
+            object instance = Activator.CreateInstance(t);
+            UpdatableToken token = new UpdatableToken(instance);
+            foreach (PropertyInfo p in t.GetProperties())
+            {
+                object generatedValue;
+                PropertyInfo property = p;
+                if (this.IsCollectionProperty(property))
+                {
+                    Type collectionType = this.GetCollectionPropertyType(GetResourceTypeFullName(t), property.Name);
+                    if (collectionType != null)
+                    {
+                        object newCollection = Activator.CreateInstance(collectionType);
+                        token.PendingPropertyUpdates[property.Name] = newCollection;
+                        this.pendingChanges.Add(delegate {
+                            property.SetValue(instance, newCollection, null);
+                        });
+                    }
+                }
+                if (this.TryGetStoreGeneratedValue(fullTypeName, property.Name, out generatedValue))
+                {
+                    token.PendingPropertyUpdates[property.Name] = generatedValue;
+                    this.pendingChanges.Add(delegate {
+                        property.SetValue(instance, generatedValue, null);
+                    });
+                }
+            }
+            return token;
+        }
+
+        protected virtual bool IsCollectionProperty(PropertyInfo propertyInfo)
+        {
+            return ((typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType) && (propertyInfo.PropertyType != typeof(string))) && (propertyInfo.PropertyType != typeof(byte[])));
+        }
+
+        public virtual void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName");
+            ExceptionUtilities.CheckArgumentNotNull(resourceToBeRemoved, "resourceToBeRemoved");
+            UpdatableToken.AssertIsToken(targetResource, "targetResource");
+            resourceToBeRemoved = UpdatableToken.AssertIsTokenAndResolve(resourceToBeRemoved, "resourceToBeRemoved");
+            IList list = this.GetValue(targetResource, propertyName) as IList;
+            ExceptionUtilities.CheckObjectNotNull(list, "Property '{0}' on type '{1}' was not a list", new object[] { propertyName, targetResource.GetType().Name });
+            this.pendingChanges.Add(delegate {
+                list.Remove(resourceToBeRemoved);
+            });
+        }
+
+        private void RemoveResourceFromCollectionOnTargetResourceMatch(object targetResource, NavigationPropertyInfo navigationPropertyInfo, object currentEntityInstance)
+        {
+            IEnumerable childCollectionObject = navigationPropertyInfo.PropertyInfo.GetValue(currentEntityInstance, null) as IEnumerable;
+            if (childCollectionObject.Cast<object>().Any<object>(delegate (object o) {
+                return o == targetResource;
+            }))
+            {
+                MethodInfo removeMethod = navigationPropertyInfo.PropertyInfo.PropertyType.GetMethod("Remove");
+                this.pendingChanges.Add(delegate {
+                    removeMethod.Invoke(childCollectionObject, new object[] { targetResource });
+                });
+            }
+        }
+
+        public virtual object ResetResource(object resource)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(resource, "resource");
+            UpdatableToken token = UpdatableToken.AssertIsToken(resource, "resource");
+            resource = token.Resource;
+            token = new UpdatableToken(resource);
+            object newInstance = Activator.CreateInstance(resource.GetType());
+            ExceptionUtilities.CheckObjectNotNull(newInstance, "Cannot reset resource because unable to creating new instance of type '{0}' returns null", new object[] { resource.GetType().Name });
+            foreach (string propertyToReset in this.MetadataHelper.GetPropertiesToReset(GetResourceTypeFullName(resource.GetType())))
+            {
+                PropertyInfo pi = newInstance.GetType().GetProperty(propertyToReset);
+                ExceptionUtilities.CheckObjectNotNull(pi, "Cannot reset resource because unable to find property '{0}'", new object[] { propertyToReset });
+                object newValue = pi.GetValue(newInstance, null);
+                this.pendingChanges.Add(delegate {
+                    pi.SetValue(resource, newValue, null);
+                });
+                token.PendingPropertyUpdates[propertyToReset] = newValue;
+            }
+            return token;
+        }
+
+        public virtual object ResolveResource(object resource)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(resource, "resource");
+            return UpdatableToken.AssertIsTokenAndResolve(resource, "resource");
+        }
+
+        public virtual void SaveChanges()
+        {
+            foreach (Action pendingChange in this.pendingChanges)
+            {
+                pendingChange();
+            }
+            this.pendingChanges.Clear();
+            foreach (object deleted in this.deletedObjects)
+            {
+                foreach (object entity in this.ResourceSetsStorage.SelectMany<KeyValuePair<string, IList>, object>(delegate (KeyValuePair<string, IList> p) {
+                    return p.Value.Cast<object>();
+                }))
+                {
+                    ExceptionUtilities.Assert(!object.ReferenceEquals(deleted, entity), "Found deleted entity!", new object[0]);
+                    foreach (PropertyInfo propertyInfo in entity.GetType().GetProperties())
+                    {
+                        object value = propertyInfo.GetValue(entity, null);
+                        ExceptionUtilities.Assert(!object.ReferenceEquals(deleted, value), "Found deleted entity!", new object[0]);
+                        IEnumerable enumerable = value as IEnumerable;
+                        if (enumerable != null)
+                        {
+                            foreach (object valueElement in enumerable.Cast<object>())
+                            {
+                                ExceptionUtilities.Assert(!object.ReferenceEquals(deleted, valueElement), "Found deleted entity!", new object[0]);
+                            }
+                        }
+                    }
+                }
+            }
+            this.deletedObjects.Clear();
+        }
+
+        protected virtual void SetCollectionPropertyValue(object targetResource, PropertyInfo propertyInfo, IEnumerable propertyValue)
+        {
+            object collection;
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            ExceptionUtilities.CheckArgumentNotNull(propertyInfo, "propertyInfo");
+            ExceptionUtilities.CheckArgumentNotNull(propertyValue, "propertyValue");
+            Type collectionType = this.GetCollectionPropertyType(GetResourceTypeFullName(propertyInfo.ReflectedType), propertyInfo.Name);
+            ExceptionUtilities.CheckObjectNotNull(collectionType, "Could not infer collection type for property", new object[0]);
+            propertyValue = propertyValue.Cast<object>().Select<object, object>(delegate (object o) {
+                return UpdatableToken.ResolveIfToken(o);
+            });
+            ConstructorInfo enumerableConstructor = collectionType.GetConstructor(new Type[] { typeof(IEnumerable) });
+            if (enumerableConstructor != null)
+            {
+                collection = enumerableConstructor.Invoke(new object[] { propertyValue });
+            }
+            else if (collectionType.IsGenericType && (collectionType.GetGenericArguments().Count<Type>() == 1))
+            {
+                Type typeArgument = collectionType.GetGenericArguments().Single<Type>();
+                ConstructorInfo typedEnumerableConstructor = collectionType.GetConstructor(new Type[] { typeof(IEnumerable<>).MakeGenericType(new Type[] { typeArgument }) });
+                if (typedEnumerableConstructor != null)
+                {
+                    object typedEnumerable = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(new Type[] { typeArgument }).Invoke(null, new object[] { propertyValue });
+                    collection = typedEnumerableConstructor.Invoke(new object[] { typedEnumerable });
+                }
+                else
+                {
+                    MethodInfo typedAddMethod = collectionType.GetMethod("Add", new Type[] { typeArgument });
+                    ExceptionUtilities.CheckObjectNotNull(typedAddMethod, "Could not find constructor or add method for type: " + collectionType.FullName, new object[0]);
+                    collection = Activator.CreateInstance(collectionType);
+                    foreach (object element in propertyValue)
+                    {
+                        typedAddMethod.Invoke(collection, new object[] { element });
+                    }
+                }
+            }
+            else
+            {
+                MethodInfo addMethod = collectionType.GetMethod("Add");
+                ExceptionUtilities.CheckObjectNotNull(addMethod, "Could not find constructor or add method for type: " + collectionType.FullName, new object[0]);
+                collection = Activator.CreateInstance(collectionType);
+                foreach (object element in propertyValue)
+                {
+                    addMethod.Invoke(collection, new object[] { element });
+                }
+            }
+            propertyInfo.SetValue(targetResource, collection, null);
+        }
+
+        public virtual void SetConcurrencyValues(object resourceCookie, bool? checkForEquality, IEnumerable<KeyValuePair<string, object>> concurrencyValues)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(resourceCookie, "resourceCookie");
+            ExceptionUtilities.ThrowDataServiceExceptionIfFalse(checkForEquality.HasValue, 0x1a1, "Missing concurrency token for update operation", new object[0]);
+            ExceptionUtilities.Assert(checkForEquality.Value, "Should not be called with check for equality parameter equal to false", new object[0]);
+            ExceptionUtilities.CheckArgumentNotNull(concurrencyValues, "concurrencyValues");
+            if (concurrencyValues.Any<KeyValuePair<string, object>>())
+            {
+                resourceCookie = UpdatableToken.AssertIsTokenAndResolve(resourceCookie, "resourceCookie");
+                ExceptionUtilities.ThrowDataServiceExceptionIfFalse(CompareETagValues(this.GetConcurrencyValues(resourceCookie), concurrencyValues), 0x19c, "Concurrency tokens do not match", new object[0]);
+            }
+        }
+
+        private void SetEntityReferenceToNullOnTargetResourceMatch(object targetResource, NavigationPropertyInfo navigationPropertyInfo, object currentEntityInstance)
+        {
+            if (navigationPropertyInfo.PropertyInfo.GetValue(currentEntityInstance, null) == targetResource)
+            {
+                this.pendingChanges.Add(delegate {
+                    navigationPropertyInfo.PropertyInfo.SetValue(currentEntityInstance, null, null);
+                });
+            }
+        }
+
+        public virtual void SetReference(object targetResource, string propertyName, object propertyValue)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName");
+            if (propertyValue != null)
+            {
+                UpdatableToken.AssertIsToken(propertyValue, "propertyValue");
+            }
+            this.SetValue(targetResource, propertyName, propertyValue);
+        }
+
+        public virtual void SetValue(object targetResource, string propertyName, object propertyValue)
+        {
+            ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource");
+            ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName");
+            UpdatableToken token = UpdatableToken.AssertIsToken(targetResource, "targetResource");
+            targetResource = token.Resource;
+            token.PendingPropertyUpdates[propertyName] = propertyValue;
+            this.pendingChanges.Add(delegate {
+                object generatedValue;
+                Type t = targetResource.GetType();
+                PropertyInfo pi = t.GetProperty(propertyName);
+                ExceptionUtilities.CheckObjectNotNull(pi, "Unable to find property '{0}' on type '{1}'", new object[] { propertyName, targetResource.GetType().Name });
+                if (this.TryGetStoreGeneratedValue(GetResourceTypeFullName(t), propertyName, out generatedValue))
+                {
+                    propertyValue = generatedValue;
+                }
+                if (this.IsCollectionProperty(pi))
+                {
+                    ExceptionUtilities.CheckObjectNotNull(propertyValue, "Collection property value was null", new object[0]);
+                    IEnumerable enumerable = propertyValue as IEnumerable;
+                    ExceptionUtilities.CheckObjectNotNull(enumerable, "Collection property value was not an enumerable", new object[0]);
+                    this.SetCollectionPropertyValue(targetResource, pi, enumerable);
+                }
+                else
+                {
+                    propertyValue = UpdatableToken.ResolveIfToken(propertyValue);
+                    pi.SetValue(targetResource, propertyValue, null);
+                }
+            });
+        }
+
+        protected virtual bool TryGetStoreGeneratedValue(string fullTypeName, string propertyName, out object propertyValue)
+        {
+            propertyValue = null;
+            return false;
+        }
+
+        private void ValidateResourceType(object targetResource, string fullTypeName)
+        {
+            ExceptionUtilities.Assert(this.MetadataHelper.FindClrTypeByFullName(fullTypeName).IsAssignableFrom(targetResource.GetType()), "Invalid uri specified. expected type: '{0}', actual type: '{1}'", new object[] { fullTypeName, targetResource.GetType().FullName });
+        }
+
+        // Properties
+        internal ReflectionMetadataHelper MetadataHelper { get; set; }
+
+        internal Dictionary<string, IList> ResourceSetsStorage
+        {
+            get
+            {
+                Dictionary<string, IList> resourceSetsLookup = null;
+                Type currentContextType = base.GetType();
+                ExceptionUtilities.Assert(resourceSetsByContextTypeStorage.TryGetValue(currentContextType, out resourceSetsLookup), "Cannot find resource sets by the context type '{0}'", new object[] { currentContextType });
+                return resourceSetsLookup;
+            }
+        }
+
+        #region Inner types.
+    
+        internal class ReflectionMetadataHelper
+        {
+            // Fields
+            private ReflectionDataContext reflectionDataContext;
+
+            // Methods
+            public ReflectionMetadataHelper(ReflectionDataContext reflectionDataContext)
+            {
+                this.reflectionDataContext = reflectionDataContext;
+            }
+
+            public Type FindClrTypeByFullName(string resourceTypeFullName)
+            {
+                Type type = this.reflectionDataContext.GetType().Assembly.GetTypes().Where<Type>(delegate (Type t) {
+                    return (ReflectionDataContext.GetResourceTypeFullName(t) == resourceTypeFullName);
+                }).FirstOrDefault<Type>();
+                ExceptionUtilities.CheckObjectNotNull(type, "Unable to find type '{0}'", new object[] { resourceTypeFullName });
+                return type;
+            }
+
+            public string[] GetETagPropertiesOfType(string fullTypeName)
+            {
+                Type type = this.FindClrTypeByFullName(fullTypeName);
+                List<string> etags = new List<string>();
+                foreach (ETagAttribute customAttribute in type.GetCustomAttributes(typeof(ETagAttribute), true))
+                {
+                    etags.AddRange(customAttribute.PropertyNames);
+                }
+                
+                return etags.ToArray();
+            }
+
+            public string[] GetKeyProperties(string fullTypeName)
+            {
+                Type type = this.FindClrTypeByFullName(fullTypeName);
+                List<string> keyPropertyList = new List<string>();
+                foreach (PropertyInfo keyProperty in type.GetProperties().Where(pi => pi.Name.Contains("ID")))
+                {
+                    keyPropertyList.Add(keyProperty.Name);
+                }
+
+                foreach (DataServiceKeyAttribute customAttribute in type.GetCustomAttributes(typeof(DataServiceKeyAttribute), true))
+                {
+                    keyPropertyList.AddRange(customAttribute.KeyNames);
+                }
+                
+                return keyPropertyList.ToArray();
+            }
+
+            public NavigationPropertyInfo[] GetNavigationProperties(string fullTypeName)
+            {
+                Type type = this.FindClrTypeByFullName(fullTypeName);
+                var navigationProperties = new List<NavigationPropertyInfo>();
+                var keyProperties = new List<string>(this.GetKeyProperties(fullTypeName));
+                foreach (PropertyInfo pi in type.GetProperties())
+                {
+                    if (!keyProperties.Contains(pi.Name))
+                    {
+                        if (this.IsTypeAnEntityType(pi.PropertyType))
+                        {
+                            navigationProperties.Add(new NavigationPropertyInfo(pi, null));
+                        }
+
+                        if (pi.PropertyType.IsGenericType && ((pi.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) || (pi.PropertyType.GetGenericTypeDefinition() == typeof(Collection<>))))
+                        {
+                            Type elementType = pi.PropertyType.GetGenericArguments()[0];
+                            if (this.IsTypeAnEntityType(elementType))
+                            {
+                                navigationProperties.Add(new NavigationPropertyInfo(pi, elementType));
+                            }
+                        }
+                    }
+                }
+
+                return navigationProperties.ToArray();
+            }
+
+            public string[] GetPropertiesToReset(string fullTypeName)
+            {
+                Type type = this.FindClrTypeByFullName(fullTypeName);
+                var keyProperties = new List<string>(this.GetKeyProperties(fullTypeName));
+                var navigationProperties = new List<string>(this.GetNavigationProperties(fullTypeName).Select(ni =>ni.PropertyInfo.Name));
+                return type.GetProperties().Where(
+                    pi => !keyProperties.Contains(pi.Name) && !navigationProperties.Contains(pi.Name)
+                ).Select(pi => pi.Name).ToArray();
+            }
+
+            public string[] GetResourceSetNames()
+            {
+                return this.reflectionDataContext.GetType().GetProperties().Where(
+                    pi => pi.PropertyType.IsGenericType && (pi.PropertyType.GetGenericTypeDefinition() == typeof(IQueryable<>))
+                ).Select(pi => pi.Name).ToArray();
+            }
+
+            public Type GetResourceTypeOfSet(string resourceSetName)
+            {
+                PropertyInfo resourceSetPropertyInfo = this.reflectionDataContext.GetType().GetProperties().Where(pi => pi.Name == resourceSetName).FirstOrDefault();
+                ExceptionUtilities.CheckObjectNotNull(resourceSetPropertyInfo, "Error finding type of set '{0}'", new object[] { resourceSetName });
+                return resourceSetPropertyInfo.PropertyType.GetGenericArguments()[0];
+            }
+
+            public bool IsTypeAnEntityType(Type t)
+            {
+                foreach (string setName in this.GetResourceSetNames())
+                {
+                    if (this.GetResourceTypeOfSet(setName).IsAssignableFrom(t))
+                    {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+        }
+
+        internal static class ExceptionUtilities
+        {
+            // Methods
+            public static void Assert(bool condition, string errorMessage, params object[] messageArguments)
+            {
+                if (!condition)
+                {
+                    throw new InvalidOperationException("Assertion failed: " + string.Format(CultureInfo.InvariantCulture, errorMessage, messageArguments));
+                }
+            }
+
+            public static void CheckArgumentNotNull(object argument, string argumentName)
+            {
+                if (argument == null)
+                {
+                    throw new ArgumentNullException(argumentName);
+                }
+            }
+
+            public static void CheckCollectionNotEmpty<TElement>(IEnumerable<TElement> argument, string argumentName)
+            {
+                CheckArgumentNotNull(argument, argumentName);
+                if (!argument.Any<TElement>())
+                {
+                    throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Collection argument '{0}' must have at least one element.", new object[] { argumentName }));
+                }
+            }
+
+            public static void CheckObjectNotNull(object value, string exceptionMessageFormatText, params object[] messageArguments)
+            {
+                Assert(exceptionMessageFormatText != null, "message cannnot be null", new object[0]);
+                Assert(messageArguments != null, "messageArguments cannnot be null", new object[0]);
+                if (value == null)
+                {
+                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, exceptionMessageFormatText, messageArguments));
+                }
+            }
+
+            public static void ThrowDataServiceExceptionIfFalse(bool condition, int statusCode, string errorMessage, params object[] messageArguments)
+            {
+                if (!condition)
+                {
+                    throw new DataServiceException(statusCode, string.Format(CultureInfo.InvariantCulture, errorMessage, messageArguments));
+                }
+            }
+        }
+
+        public class UpdatableToken
+        {
+            // Methods
+            public UpdatableToken(object resource)
+            {
+                ExceptionUtilities.CheckArgumentNotNull(resource, "resource");
+                this.Resource = resource;
+                this.PendingPropertyUpdates = new Dictionary<string, object>();
+            }
+
+            public static UpdatableToken AssertIsToken(object resource, string name)
+            {
+                ExceptionUtilities.CheckArgumentNotNull(resource, "resource");
+                UpdatableToken token = resource as UpdatableToken;
+                ExceptionUtilities.CheckObjectNotNull(token, "{0} was not a token. Type was: '{1}'", new object[] { name, resource.GetType() });
+                return token;
+            }
+
+            public static object AssertIsTokenAndResolve(object resource, string name)
+            {
+                return AssertIsToken(resource, name).Resource;
+            }
+
+            public static object ResolveIfToken(object resource)
+            {
+                UpdatableToken token = resource as UpdatableToken;
+                if (token != null)
+                {
+                    resource = token.Resource;
+                }
+                return resource;
+            }
+
+            // Properties
+            public IDictionary<string, object> PendingPropertyUpdates { get; set; }
+
+            public object Resource { get; set; }
+        }
+
+        internal class NavigationPropertyInfo
+        {
+            // Methods
+            internal NavigationPropertyInfo(PropertyInfo pi, Type collectionElementType)
+            {
+                this.PropertyInfo = pi;
+                this.CollectionElementType = collectionElementType;
+            }
+
+            // Properties
+            public Type CollectionElementType { get; set; }
+
+            public PropertyInfo PropertyInfo { get; set; }
+        }
+
+        #endregion Inner types.
+    }
+}
\ No newline at end of file
diff --git a/JSLib/tests/code/atomreader.cs b/JSLib/tests/code/atomreader.cs
new file mode 100644
index 0000000..6391d87
--- /dev/null
+++ b/JSLib/tests/code/atomreader.cs
@@ -0,0 +1,796 @@
+/// <summary>
+/// Class used to parse the Content section of the feed to return the properties data and metadata
+/// </summary>
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Linq;
+    using System.ServiceModel.Syndication;
+    using System.Spatial;
+    using System.Xml;
+    using System.Xml.Linq;
+
+    public static class AtomReader
+    {
+        const string atomXmlNs = "http://www.w3.org/2005/Atom";
+        const string gmlXmlNs = "http://www.opengis.net/gml";
+        const string odataRelatedPrefix = "http://schemas.microsoft.com/ado/2007/08/dataservices/related";
+        const string odataRelatedLinksPrefix = "http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks";
+        const string odataXmlNs = "http://schemas.microsoft.com/ado/2007/08/dataservices";
+        const string odataMetaXmlNs = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";
+        const string odataEditMediaPrefix = "http://schemas.microsoft.com/ado/2007/08/dataservices/edit-media";
+        const string odataMediaResourcePrefix = "http://schemas.microsoft.com/ado/2007/08/dataservices/mediaresource";
+
+        const string hrefAttribute = "href";
+        const string titleElement = "title";
+        const string workspaceElement = "workspace";
+        const string workspacesProperty = "workspaces";
+        const string collectionElement = "collection";
+        const string collectionsProperty = "collections";
+        const string extensionsProperty = "extensions";
+        static string baseUri = string.Empty;
+
+        /// <summary>
+        /// Creates a service document object
+        /// </summary>
+        /// <param name="container">The XML container</param>
+        /// <param name="uri">Uri to append to the href value</param>
+        /// <returns>The service document JsonObject</returns>
+        public static JsonObject ReadServiceDocument(TextReader payload, string baseUri)
+        {
+            JsonObject jsonObject = new JsonObject();
+            XElement container = XElement.Load(payload);
+
+            if (container != null && container.HasElements)
+            {
+                jsonObject["workspaces"] =
+                    container
+                        .Elements()
+                            .Where(element => element.Name.LocalName.Equals(workspaceElement))
+                            .Select(element => ReadWorkspaceObject(element, baseUri))
+                            .ToArray();
+
+                jsonObject["extensions"] =
+                    container
+                        .Elements()
+                            .Where(element => !element.Name.LocalName.Equals(workspaceElement))
+                            .Select(element => ReadExtensionElement(element))
+                            .ToArray();
+            }
+
+            return jsonObject;
+        }
+
+        public static JsonObject ReadEntry(TextReader payload)
+        {
+            SyndicationItem item = SyndicationItem.Load(XmlReader.Create(payload));
+            return ReadEntry(item);
+        }
+
+        public static JsonObject ReadFeed(TextReader payload)
+        {
+            SyndicationFeed feed = SyndicationFeed.Load(XmlReader.Create(payload));
+            JsonObject feedData = new JsonObject();
+            JsonObject feedMetadata = new JsonObject();
+
+            feedData["results"] = feed.Items.Select(item => ReadEntry(item)).ToArray();
+            feedData["__metadata"] = feedMetadata;
+
+            feedMetadata["feed_extensions"] = feed.AttributeExtensions.Select(pair => ReadExtension(pair)).ToArray();
+
+            if (feed.Id != null)
+            {
+                feedMetadata["uri"] = feed.Id;
+                feedMetadata["uri_extensions"] = new JsonObject[] { };
+            }
+
+            if (feed.Title != null)
+            {
+                feedMetadata["title"] = feed.Title.Text;
+                feedMetadata["title_extensions"] = GetTitleExtensions(feed.Title);
+            }
+
+            SyndicationLink feedSelfLink = GetLink("self", feed.Links);
+            if (feedSelfLink != null)
+            {
+                feedMetadata["self"] = feedSelfLink.GetAbsoluteUri().AbsoluteUri;
+                feedMetadata["self_extensions"] = GetLinkExtensions(feedSelfLink);
+            }
+
+            long? count = GetInlineCount(feed);
+            if (count.HasValue)
+            {
+                feedData["__count"] = count.Value;
+            }
+
+            SyndicationLink feedNextLink = GetLink("next", feed.Links);
+            if (feedNextLink != null)
+            {
+                feedData["__next"] = feedNextLink.GetAbsoluteUri().AbsoluteUri;
+                feedMetadata["next_extensions"] = GetLinkExtensions(feedNextLink);
+            }
+
+            return feedData;
+        }
+
+        private static JsonObject ReadEntry(SyndicationItem item)
+        {
+            SyndicationLink entryEditLink = GetLink("edit", item.Links);
+            SyndicationCategory entryCategory = item.Categories.FirstOrDefault();
+
+            XElement propertiesElement = GetPropertiesElement(item);
+            JsonObject entryData = ReadObject(propertiesElement);
+            entryData = JsonObject.Merge(entryData, ReadNavigationProperties(item));
+            entryData = JsonObject.Merge(entryData, ReadNamedStreams(item));
+
+            JsonObject propertiesMetadata = ReadPropertiesMetadata(propertiesElement);
+            propertiesMetadata = JsonObject.Merge(propertiesMetadata, ReadNavigationPropertiesMetadata(item));
+            propertiesMetadata = JsonObject.Merge(propertiesMetadata, ReadNamedStreamMetadata(item));
+
+            JsonObject entryMetadata = new JsonObject();
+            entryData["__metadata"] = entryMetadata;
+            entryMetadata["properties"] = propertiesMetadata;
+
+            if (item.Id != null)
+            {
+                entryMetadata["uri"] = item.Id;
+                entryMetadata["uri_extensions"] = new JsonObject[] { };
+            }
+
+            if (entryCategory != null)
+            {
+                entryMetadata["type"] = entryCategory.Name;
+                entryMetadata["type_extensions"] = new JsonObject[] { };
+            }
+
+            if (entryEditLink != null)
+            {
+                entryMetadata["edit"] = entryEditLink.GetAbsoluteUri().AbsoluteUri;
+                entryMetadata["edit_link_extensions"] = GetLinkExtensions(entryEditLink);
+            }
+
+            return entryData;
+        }
+
+        private static JsonObject ReadExtension(KeyValuePair<XmlQualifiedName, string> pair)
+        {
+            return ReaderUtils.CreateExtension(pair.Key.Name, pair.Key.Namespace, pair.Value);
+        }
+
+        private static string GetCollectionType(string type)
+        {
+            if (type != null && type.StartsWith("Collection("))
+            {
+                int start = 11;
+                int end = type.IndexOf(")") - 11;
+                return type.Substring(start, end);
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Find the m:properties element within a feed entry
+        /// </summary>
+        /// <param name="item">The feed entry</param>
+        /// <returns>The m:properties element</returns>
+        private static XElement GetPropertiesElement(SyndicationItem item)
+        {
+            // Check if the m:properties element is within the content element
+            XmlSyndicationContent xmlContent = item.Content as XmlSyndicationContent;
+            if (xmlContent != null)
+            {
+                XElement contentElement = XElement.Load(xmlContent.GetReaderAtContent());
+                return contentElement.Elements().FirstOrDefault(e => e.Name == XName.Get("properties", odataMetaXmlNs));
+            }
+            // If we're here, then we are dealing with a feed that has an MLE
+            // i.e. the m:properties element is a peer of the content element, and shows up
+            // in the elementExtensions instead
+            SyndicationElementExtension propertiesElementExtension = item.ElementExtensions.FirstOrDefault(e => e.OuterName.Equals("properties"));
+            if (propertiesElementExtension != null)
+            {
+                XNode propertiesElement = XNode.ReadFrom(propertiesElementExtension.GetReader());
+                return (XElement)propertiesElement;
+            }
+
+            throw new NotSupportedException("Unsupported feed entry format");
+        }
+
+        /// <summary>
+        /// Gets the inline count within a feed
+        /// </summary>
+        /// <param name="feed">The feed</param>
+        /// <returns>The inline count, or null if none exists</returns>
+        private static long? GetInlineCount(SyndicationFeed feed)
+        {
+            SyndicationElementExtension countElementExtension = feed.ElementExtensions.SingleOrDefault(extension =>
+                extension.OuterName.Equals("count", StringComparison.OrdinalIgnoreCase) &&
+                extension.OuterNamespace.Equals(odataMetaXmlNs));
+
+            if (countElementExtension != null)
+            {
+                XElement countElement = (XElement)XNode.ReadFrom(countElementExtension.GetReader());
+                return XmlConvert.ToInt64(countElement.Value);
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Gets the link with the specified relationship type
+        /// </summary>
+        /// <param name="rel">Relationship type</param>
+        /// <param name="links">The set of links to search from</param>
+        /// <returns>The link with the specified relationship type, or null if none exists</returns>
+        private static SyndicationLink GetLink(string rel, IEnumerable<SyndicationLink> links)
+        {
+            return links.SingleOrDefault(link => link.RelationshipType.Equals(rel, StringComparison.InvariantCultureIgnoreCase));
+        }
+
+        private static IEnumerable<SyndicationLink> GetLinks(string rel, IEnumerable<SyndicationLink> links)
+        {
+            return links.Where(link => link.RelationshipType.StartsWith(rel, StringComparison.Ordinal));
+        }
+
+        //TODO refactor the extraction of extensions into extension elements and extension attribute methods.
+        private static JsonObject[] GetLinkExtensions(SyndicationLink link)
+        {
+            List<JsonObject> extensions = new List<JsonObject>();
+            //TODO: fix the inclusion of title as extension.  Title attribute is not required in the link element and its
+            //inclusion as an extension should be tested for the precesence of the attribute in the xml element.  Unfortunately,
+            //SyndicationLink doesn't allow for accessing the underlying XML document.. perhaps using an AtomFormatter10?? 
+            extensions.Add(ReaderUtils.CreateExtension("title", null, link.Title));
+            extensions.AddRange(link.AttributeExtensions.Select(pair => ReadExtension(pair)));
+
+            return extensions.ToArray();
+        }
+
+        private static JsonObject[] GetTitleExtensions(TextSyndicationContent title)
+        {
+            List<JsonObject> extensions = new List<JsonObject>();
+            extensions.Add(ReaderUtils.CreateExtension("type", null, title.Type));
+            extensions.AddRange(title.AttributeExtensions.Select(pair => ReadExtension(pair)));
+
+            return extensions.ToArray();
+        }
+
+        /// <summary>
+        /// Gets the "type" value from a property element
+        /// </summary>
+        /// <param name="propertyElement">The property element</param>
+        /// <returns>The "type" value, or default (Edm.String) if none specified</returns>
+        private static string GetTypeAttribute(XElement propertyElement)
+        {
+            XAttribute typeAttribute = propertyElement.Attribute(XName.Get("type", odataMetaXmlNs));
+            if (typeAttribute == null)
+            {
+                if (propertyElement.HasElements)
+                {
+                    return null;
+                }
+                return "Edm.String";
+            }
+            return typeAttribute.Value;
+        }
+
+        private static bool HasTypeAttribute(XElement propertyElement)
+        {
+            return propertyElement.Attribute(XName.Get("type", odataMetaXmlNs)) != null;
+        }
+
+        private static bool IsCollectionProperty(XElement propertyElement)
+        {
+            string type = GetTypeAttribute(propertyElement);
+            if (type != null && type.StartsWith("Collection("))
+            {
+                return true;
+
+            }
+            return propertyElement.Elements().Count(pe => pe.Name == XName.Get("element", odataXmlNs)) > 1;
+        }
+
+        private static JsonObject ReadWorkspaceObject(XElement container, string baseUri)
+        {
+            JsonObject jsonObject = new JsonObject();
+
+            jsonObject["collections"] =
+                container
+                    .Elements()
+                        .Where(element => element.Name.LocalName.Equals("collection"))
+                        .Select(element => ReadWorkspaceCollections(element, baseUri))
+                        .ToArray();
+
+            jsonObject["extensions"] =
+                container
+                    .Elements()
+                        .Where(element =>
+                            !(element.Name.LocalName.Equals("collection") ||
+                              element.Name.LocalName.Equals("title")))
+                        .Select(element => ReadExtensionElement(element))
+                        .ToArray();
+
+            jsonObject["title"] =
+                container
+                    .Elements()
+                    .Where(element => element.Name.LocalName.Equals("title"))
+                    .First()
+                    .Value;
+
+            return jsonObject;
+        }
+
+        private static JsonObject ReadWorkspaceCollections(XElement container, string baseUri)
+        {
+            JsonObject jsonObject = new JsonObject();
+            string title = string.Empty;
+
+            jsonObject["extensions"] =
+                container
+                    .Elements()
+                        .Where(element =>
+                            !(element.Name.LocalName.Equals(collectionElement) ||
+                              element.Name.LocalName.Equals(titleElement)))
+                        .Select(element => ReadExtensionElement(element))
+                        .ToArray();
+
+            jsonObject["title"] =
+                container
+                    .Elements()
+                        .Where(element => element.Name.LocalName.Equals("title"))
+                        .First()
+                        .Value;
+
+            IEnumerable<XAttribute> hrefAttributes =
+                container
+                    .Attributes()
+                        .Where(element => element.Name.LocalName.Equals("href"));
+
+            jsonObject["href"] = baseUri + hrefAttributes.First().Value;
+
+            return jsonObject;
+        }
+
+        private static JsonObject ReadExtensionElement(XElement element)
+        {
+            JsonObject jsonObject = ReaderUtils.CreateExtension(element.Name.LocalName, element.BaseUri, null);
+            jsonObject.Remove("value");
+            jsonObject["attributes"] = ReadExtensionAttributes(element);
+            jsonObject["children"] = element.Elements().Select(child => ReadExtensionElement(element)).ToArray();
+
+            return jsonObject;
+        }
+
+        private static JsonObject ReadExtensionAttribute(XAttribute attribute)
+        {
+            return ReaderUtils.CreateExtension(attribute.Name.LocalName, attribute.BaseUri, attribute.Value);
+        }
+
+        private static JsonObject[] ReadExtensionAttributes(XElement container)
+        {
+            List<JsonObject> attributes = new List<JsonObject>();
+            foreach (XAttribute attribute in container.Attributes())
+            {
+                attributes.Add(ReadExtensionAttribute(attribute));
+            }
+            return attributes.ToArray();
+        }
+
+        private static JsonObject ReadNamedStreamMetadata(SyndicationItem item)
+        {
+            JsonObject propertiesMetadata = new JsonObject();
+            JsonObject streamMetadata;
+            string propertyName;
+
+            foreach (SyndicationLink link in GetLinks(odataEditMediaPrefix, item.Links))
+            {
+                streamMetadata = new JsonObject();
+                streamMetadata["edit_media_extensions"] = GetLinkExtensions(link);
+                streamMetadata["media_src_extensions"] = new JsonObject[0];
+
+                propertyName = link.RelationshipType.Substring(odataEditMediaPrefix.Length + 1);
+                propertiesMetadata[propertyName] = streamMetadata;
+            }
+
+            foreach (SyndicationLink link in GetLinks(odataMediaResourcePrefix, item.Links))
+            {
+                streamMetadata = new JsonObject();
+                streamMetadata["media_src_extensions"] = GetLinkExtensions(link);
+
+                propertyName = link.RelationshipType.Substring(odataMediaResourcePrefix.Length + 1);
+                if (propertiesMetadata.ContainsKey(propertyName))
+                {
+                    streamMetadata = JsonObject.Merge((JsonObject)propertiesMetadata[propertyName], streamMetadata);
+                }
+                propertiesMetadata[propertyName] = streamMetadata;
+            }
+            return propertiesMetadata;
+        }
+
+        private static JsonObject ReadNamedStreams(SyndicationItem item)
+        {
+            // Not very elegant, but quick and easy, do it in two passes.
+            JsonObject streams = new JsonObject();
+            JsonObject streamValue;
+            JsonObject mediaResource;
+            string propertyName;
+
+            foreach (SyndicationLink link in GetLinks(odataEditMediaPrefix, item.Links))
+            {
+                propertyName = link.RelationshipType.Substring(odataEditMediaPrefix.Length + 1);
+                streamValue = new JsonObject();
+                mediaResource = new JsonObject();
+
+                streams[propertyName] = streamValue;
+
+                streamValue["__mediaresource"] = mediaResource;
+
+                mediaResource["edit_media"] = link.GetAbsoluteUri().AbsoluteUri;
+                mediaResource["content_type"] = link.MediaType;
+                mediaResource["media_src"] = link.GetAbsoluteUri().AbsoluteUri;
+
+                var etagAttributeName = new XmlQualifiedName("etag", odataMetaXmlNs);
+                if (link.AttributeExtensions.ContainsKey(etagAttributeName))
+                {
+                    mediaResource["media_etag"] = link.AttributeExtensions[etagAttributeName];
+                    link.AttributeExtensions.Remove(etagAttributeName);
+                }
+            }
+
+            foreach (SyndicationLink link in GetLinks(odataMediaResourcePrefix, item.Links))
+            {
+                propertyName = link.RelationshipType.Substring(odataMediaResourcePrefix.Length + 1);
+                mediaResource = new JsonObject();
+                mediaResource["content_type"] = link.MediaType;
+                mediaResource["media_src"] = link.GetAbsoluteUri().AbsoluteUri;
+
+                if (streams.ContainsKey(propertyName))
+                {
+                    streamValue = streams[propertyName] as JsonObject;
+                    mediaResource = JsonObject.Merge(streamValue["__mediaresource"] as JsonObject, mediaResource);
+                }
+                else
+                {
+                    streamValue = new JsonObject();
+                }
+                streamValue["__mediaresource"] = mediaResource;
+                streams[propertyName] = streamValue;
+            }
+            return streams;
+        }
+
+        private static JsonObject ReadNavigationProperties(SyndicationItem item)
+        {
+            JsonObject navProperties = new JsonObject();
+            SyndicationElementExtension inline;
+
+            string propertyName;
+            JsonObject propertyValue = null;
+
+            foreach (SyndicationLink link in GetLinks(odataRelatedPrefix, item.Links))
+            {
+                propertyName = link.RelationshipType.Substring(odataRelatedPrefix.Length + 1);
+                inline = link.ElementExtensions.SingleOrDefault(e =>
+                    odataMetaXmlNs.Equals(e.OuterNamespace, StringComparison.Ordinal) &&
+                    e.OuterName.Equals("inline", StringComparison.Ordinal));
+
+                if (inline != null)
+                {
+                    XElement inlineElement = (XElement)XNode.ReadFrom(inline.GetReader());
+                    XElement innerElement = inlineElement.Elements().FirstOrDefault();
+
+                    if (innerElement != null)
+                    {
+                        // By default the inner feed/entry does not have the xml:base attribute, so we need to
+                        // add it so that the parsed SyndicationFeed or SyndicationItem retains the baseUri
+                        if (link.BaseUri != null)
+                        {
+                            innerElement.SetAttributeValue(XNamespace.Xml + "base", link.BaseUri.OriginalString);
+                        }
+
+                        // We are converting to a string before creating the reader to strip out extra indenting,
+                        // otherwise the reader creates extra XmlText nodes that SyndicationFeed/SyndicationItem cannot handle
+                        try
+                        {
+                            propertyValue = ReadFeed(new StringReader(innerElement.ToString()));
+                        }
+                        catch (XmlException)
+                        {
+                            // Try with entry instead .. 
+
+                            propertyValue = ReadEntry(new StringReader(innerElement.ToString()));
+                        }
+                    }
+                }
+                else
+                {
+                    JsonObject deferred = new JsonObject();
+                    deferred["uri"] = link.GetAbsoluteUri().AbsoluteUri;
+
+                    propertyValue = new JsonObject();
+                    propertyValue["__deferred"] = deferred;
+                }
+                navProperties[propertyName] = propertyValue;
+            }
+            return navProperties;
+        }
+
+        private static JsonObject ReadNavigationPropertiesMetadata(SyndicationItem item)
+        {
+            JsonObject propertiesMetadata = new JsonObject();
+            JsonObject navMetadata;
+            string propertyName;
+
+            foreach (SyndicationLink link in GetLinks(odataRelatedPrefix, item.Links))
+            {
+                navMetadata = new JsonObject();
+                navMetadata["extensions"] = GetLinkExtensions(link).Where(e =>
+                    !(string.Equals(e["name"] as string, "inline", StringComparison.Ordinal) &&
+                        string.Equals(e["namespaceURI"] as string, odataMetaXmlNs, StringComparison.Ordinal))
+                ).ToArray();
+
+                propertyName = link.RelationshipType.Substring(odataRelatedPrefix.Length + 1);
+                propertiesMetadata[propertyName] = navMetadata;
+            }
+
+            foreach (SyndicationLink link in GetLinks(odataRelatedLinksPrefix, item.Links))
+            {
+                navMetadata = new JsonObject();
+                navMetadata["associationuri"] = link.GetAbsoluteUri().AbsoluteUri;
+                navMetadata["associationuri_extensions"] = link.GetAbsoluteUri().AbsoluteUri;
+
+                propertyName = link.RelationshipType.Substring(odataRelatedLinksPrefix.Length + 1);
+                if (propertiesMetadata.ContainsKey(propertyName))
+                {
+                    navMetadata = JsonObject.Merge(propertiesMetadata[propertyName] as JsonObject, navMetadata);
+                }
+                propertiesMetadata[propertyName] = navMetadata;
+            }
+
+            return propertiesMetadata;
+        }
+
+        private static JsonObject ReadPropertiesMetadata(XElement container)
+        {
+            JsonObject json = null;
+            if (container != null && container.Elements().Any(e => e.Name.NamespaceName == odataXmlNs))
+            {
+                json = new JsonObject();
+                foreach (XElement propertyElement in container.Elements())
+                {
+                    json[propertyElement.Name.LocalName] = ReadPropertyMetadata(propertyElement);
+                }
+            }
+            return json;
+        }
+
+        private static JsonObject ReadPropertyMetadata(XElement property)
+        {
+            var metadata = ReaderUtils.CreateEntryPropertyMetadata(GetTypeAttribute(property));
+
+            if (IsCollectionProperty(property))
+            {
+                string collectionType = GetCollectionType(GetTypeAttribute(property));
+                if (collectionType == null)
+                {
+                    metadata["type"] = "Collection()";
+                }
+
+                List<JsonObject> elements = new List<JsonObject>();
+                foreach (XElement item in property.Elements(XName.Get("element", odataXmlNs)))
+                {
+                    string itemType =
+                        HasTypeAttribute(item) ? GetTypeAttribute(item) :
+                        IsCollectionProperty(item) ? "Collection()" : collectionType;
+
+                    var itemMetadata = ReaderUtils.CreateEntryPropertyMetadata(itemType);
+                    if (item.Elements().Any(e => e.Name.NamespaceName == odataXmlNs))
+                    {
+                        itemMetadata["properties"] = ReadPropertiesMetadata(item);
+                    }
+                    elements.Add(itemMetadata);
+                }
+                metadata["elements"] = elements.ToArray();
+            }
+            else if (property != null && property.Elements().Any(e => e.Name.NamespaceName == odataXmlNs))
+            {
+                metadata["properties"] = ReadPropertiesMetadata(property);
+            }
+
+            return metadata;
+        }
+
+        /// <summary>
+        /// Creates a JsonObject from an XML container element (e.g. the m:properties element) of an OData ATOM feed entry. 
+        /// </summary>
+        /// <param name="container">The XML container</param>
+        /// <param name="buildValue">Function that builds a value from a property element</param>
+        /// <returns>The JsonObject containing the name-value pairs</returns>
+        private static JsonObject ReadObject(XElement container)
+        {
+            if (container == null)
+            {
+                return null;
+            }
+
+            var json = new JsonObject();
+            foreach (XElement propertyElement in container.Elements())
+            {
+                json[propertyElement.Name.LocalName] = ReadDataItem(propertyElement);
+            }
+            return json;
+        }
+
+        private static JsonObject ReadCollectionProperty(XElement property, string typeName)
+        {
+            var collectionType = GetCollectionType(typeName);
+
+            var json = new JsonObject();
+            var results = new List<object>();
+
+            foreach (XElement item in property.Elements())
+            {
+                object resultItem = ReadDataItem(item);
+                results.Add(resultItem);
+
+                JsonObject complexValue = resultItem as JsonObject;
+                if (complexValue != null)
+                {
+                    var metadata = complexValue["__metadata"] as JsonObject;
+                    if (!string.IsNullOrEmpty(collectionType) && metadata["type"] == null)
+                    {
+                        metadata["type"] = collectionType;
+                    }
+                }
+            }
+
+            json["results"] = results;
+            json["__metadata"] = ReaderUtils.CreateEntryPropertyMetadata(typeName, false);
+
+            return json;
+        }
+
+        private static JsonObject ReadComplexProperty(XElement container, string typeName)
+        {
+            JsonObject json = ReadObject(container);
+            json["__metadata"] = ReaderUtils.CreateEntryPropertyMetadata(GetTypeAttribute(container), false);
+            return json;
+        }
+
+        private static JsonObject ReadJsonSpatialProperty(XElement container, XElement gmlValue, bool isGeography)
+        {
+            GmlFormatter gmlFormatter = GmlFormatter.Create();
+            GeoJsonObjectFormatter jsonformatter = GeoJsonObjectFormatter.Create();
+
+            bool ignoreCrc = !gmlValue.Attributes().Any(a => a.Name.LocalName == "srsName");
+
+            ISpatial spatialValue;
+            if (isGeography)
+            {
+                spatialValue = gmlFormatter.Read<Geography>(gmlValue.CreateReader());
+            }
+            else
+            {
+                spatialValue = gmlFormatter.Read<Geometry>(gmlValue.CreateReader());
+            }
+
+            IDictionary<string, object> geoJsonData = jsonformatter.Write(spatialValue);
+            JsonObject json = new JsonObject();
+
+            Queue<object> geoJsonScopes = new Queue<object>();
+            Queue<object> jsonScopes = new Queue<object>();
+
+            geoJsonScopes.Enqueue(geoJsonData);
+            jsonScopes.Enqueue(json);
+
+            Func<object, object> convertScope = (scope) =>
+            {
+                object newScope =
+                        scope is List<object> || scope is object[] ? (object)new List<Object>() :
+                        scope is IDictionary<string, object> ? (object)new JsonObject() :
+                        null;
+
+                if (newScope != null)
+                {
+                    geoJsonScopes.Enqueue(scope);
+                    jsonScopes.Enqueue(newScope);
+                }
+
+                return newScope ?? scope;
+            };
+
+            while (jsonScopes.Count > 0)
+            {
+                if (jsonScopes.Peek() is JsonObject)
+                {
+                    var currentGeoJson = (IDictionary<string, object>)geoJsonScopes.Dequeue();
+                    var currentJson = (JsonObject)jsonScopes.Dequeue();
+
+                    foreach (var item in currentGeoJson)
+                    {
+                        if (!ignoreCrc || item.Key != "crs")
+                        {
+                            currentJson[item.Key] = convertScope(item.Value);
+                        }
+                    }
+                }
+                else
+                {
+                    var currentGeoJson = (IEnumerable<object>)geoJsonScopes.Dequeue();
+                    var currentJson = (List<object>)jsonScopes.Dequeue();
+
+                    foreach (var item in currentGeoJson)
+                    {
+                        currentJson.Add(convertScope(item));
+                    }
+                }
+            }
+            json["__metadata"] = ReaderUtils.CreateEntryPropertyMetadata(GetTypeAttribute(container), false);
+            return json;
+        }
+
+        public static object ReadDataItem(XElement item)
+        {
+            string typeName = GetTypeAttribute(item);
+            XElement gmlRoot = item.Elements().SingleOrDefault(e => e.Name.NamespaceName == gmlXmlNs);
+
+            if (gmlRoot != null)
+            {
+                bool isGeography = typeName.StartsWith("Edm.Geography");
+                return ReadJsonSpatialProperty(item, gmlRoot, isGeography);
+            }
+
+            bool isCollection = IsCollectionProperty(item);
+            if (item.HasElements || isCollection)
+            {
+                // Complex type, Collection Type: parse recursively
+                return isCollection ? ReadCollectionProperty(item, typeName) : ReadComplexProperty(item, typeName);
+            }
+
+            // Primitive type: null value
+            XNamespace mNamespace = item.GetNamespaceOfPrefix("m");
+            XAttribute nullAttribute = mNamespace == null ? null : item.Attribute(mNamespace.GetName("null"));
+            if (nullAttribute != null && nullAttribute.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase))
+            {
+                return null;
+            }
+
+            // Primitive type: check type and parse value accordingly
+            string value = item.Value;
+            switch (typeName)
+            {
+                case "Edm.Byte":
+                    return XmlConvert.ToByte(value);
+                case "Edm.Int16":
+                    return XmlConvert.ToInt16(value);
+                case "Edm.Int32":
+                    return XmlConvert.ToInt32(value);
+                case "Edm.SByte":
+                    return XmlConvert.ToSByte(value);
+                case "Edm.Boolean":
+                    return XmlConvert.ToBoolean(value);
+                case "Edm.Double":
+                    return XmlConvert.ToDouble(value);
+                case "Edm.Single":
+                    return XmlConvert.ToSingle(value);
+                case "Edm.Guid":
+                    return XmlConvert.ToGuid(value);
+                case "Edm.DateTime":
+                    return new JsDate(XmlConvert.ToDateTime(value, XmlDateTimeSerializationMode.Utc));
+                case "Edm.DateTimeOffset":
+                    return new JsDate(XmlConvert.ToDateTimeOffset(value));
+                case "Edm.Time":
+                    throw new NotSupportedException(typeName + " is not supported");
+                // Decimal and Int64 values are sent as strings over the wire.  This is the same behavior as WCF Data Services JSON serializer.
+                case "Edm.Decimal":
+                case "Edm.Int64":
+                case "":
+                default:
+                    return value;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/JSLib/tests/code/csdlreader.cs b/JSLib/tests/code/csdlreader.cs
new file mode 100644
index 0000000..47a331e
--- /dev/null
+++ b/JSLib/tests/code/csdlreader.cs
@@ -0,0 +1,231 @@
+/// <summary>
+/// Class used to parse csdl to create the metatdata object
+/// </summary>
+/// 
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Linq;
+    using System.Xml.Linq;
+
+
+    public static class CsdlReader
+    {
+        static readonly string knownNamespace = "http://schemas.microsoft.com";
+        static readonly string[] repeatingElements = 
+            {
+                "End", 
+                "Property", 
+                "PropertyRef", 
+                "EntitySet", 
+                "AssociationSet", 
+                "FunctionImport", 
+                "NavigationProperty", 
+                "Parameter", 
+                "Using", 
+                "EntityContainer", 
+                "EntityType", 
+                "Association", 
+                "ComplexType", 
+                "Function", 
+                "Schema"
+            };
+
+        public static JsonObject ReadCsdl(TextReader payload)
+        {
+            return BuildElementJsonObject(XElement.Load(payload));
+        }
+
+        /// <summary>
+        /// Builds the extensions element object
+        /// extensions = {
+        /// name: string, // local name of the custom XML element
+        /// namespace: string, // namespace URI of the custom XML element
+        /// value: string, // value of the custom XML element
+        /// attributes: array, // array of attribute extension objects of the custom XML element
+        /// children: array // array of element extension objects of the custom XML element };
+        /// </summary>
+        /// <param name="customElement">The custom element to be made into an extension object</param>
+        /// <returns>the custom element json object</returns>
+        static JsonObject BuildExtensionsElementObject(XElement customElement)
+        {
+            string value;
+            // customElement.Value contains the value of the element's children, but these are already
+            // captured in the children propterty.
+            if (customElement.HasElements)
+            {
+                value = null;
+            }
+            else
+            {
+                if (customElement.Value == "")
+                {
+                    value = null;
+                }
+                else
+                {
+                    value = customElement.Value;
+                }
+            }
+
+            JsonObject jsonObject = BuildBaseExtensionsObject(customElement.Name.LocalName, customElement.Name.Namespace.ToString(), value);
+
+            jsonObject["attributes"] = customElement.Attributes().Select(
+                attribute => BuildBaseExtensionsObject(attribute.Name.LocalName, attribute.Name.Namespace.ToString(), attribute.Value)
+                ).ToArray();
+            jsonObject["children"] = customElement.Elements().Select(element => BuildExtensionsElementObject(element)).ToArray();
+
+            return jsonObject;
+        }
+
+        /// <summary>
+        /// Creates a generic extension object
+        /// extensions = {
+        /// name: string, // local name of the custom XML element or attribute
+        /// namespace: string, // namespace URI of the custom XML element or attribute
+        /// value: string, // value of the custom XML element or attribute }
+        /// </summary>
+        /// <param name="name">name of the object</param>
+        /// <param name="objectNamespace">namespace of the obect</param>
+        /// <param name="value">value of the object</param>
+        /// <returns></returns>
+        static JsonObject BuildBaseExtensionsObject(string name, string objectNamespace, string value)
+        {
+            JsonObject jsonObject = new JsonObject();
+
+            jsonObject["name"] = name;
+            jsonObject["namespace"] = objectNamespace;
+            jsonObject["value"] = value;
+
+            return jsonObject;
+        }
+
+        /// <summary>
+        /// Build the attribute object 
+        /// </summary>
+        /// <param name="xmlAttributes">IEnumberable of XAttributes to build the attribute object</param>
+        /// <returns>The JsonObject containing the name-value pairs for an element's attributes</returns>
+        static JsonObject BuildAttributeJsonObject(IEnumerable<XAttribute> xmlAttributes)
+        {
+            JsonObject jsonObject = new JsonObject();
+            List<JsonObject> extensions = new List<JsonObject>();
+
+            foreach (XAttribute attribute in xmlAttributes)
+            {
+                if (!attribute.IsNamespaceDeclaration)
+                {
+                    string attributeNamespace = attribute.Name.Namespace.ToString();
+                    if (string.IsNullOrEmpty(attributeNamespace) || attributeNamespace.StartsWith(knownNamespace, StringComparison.InvariantCultureIgnoreCase))
+                    {
+                        jsonObject[MakeFirstLetterLowercase(attribute.Name.LocalName)] = attribute.Value;
+                    }
+                    else
+                    {
+                        extensions.Add(BuildBaseExtensionsObject(attribute.Name.LocalName, attribute.Name.Namespace.ToString(), attribute.Value));
+                    }
+                }
+            }
+
+            if (extensions.Count > 0)
+            {
+                jsonObject["extensions"] = extensions.ToArray();
+            }
+
+            return jsonObject;
+        }
+
+        /// <summary>
+        /// Creates a JsonObject from an XML container element with each attribute or subelement as a property
+        /// </summary>
+        /// <param name="container">The XML container</param>
+        /// <param name="buildValue">Function that builds a value from a property element</param>
+        /// <returns>The JsonObject containing the name-value pairs</returns>
+        public static JsonObject BuildElementJsonObject(XElement container)
+        {
+            if (container == null)
+            {
+                return null;
+            }
+
+            JsonObject jsonObject = new JsonObject();
+            List<JsonObject> extensions = new List<JsonObject>();
+
+            if (container.HasAttributes || container.HasElements)
+            {
+                Dictionary<string, List<JsonObject>> repeatingObjectArrays = new Dictionary<string, List<JsonObject>>();
+                JsonObject extensionObject = new JsonObject();
+
+                jsonObject = BuildAttributeJsonObject(container.Attributes());
+
+                foreach (XElement propertyElement in container.Elements())
+                {
+                    string propertyName = MakeFirstLetterLowercase(propertyElement.Name.LocalName);
+                    string properyNamespace = propertyElement.Name.Namespace.ToString();
+
+                    if (string.IsNullOrEmpty(properyNamespace) || properyNamespace.StartsWith(knownNamespace, StringComparison.InvariantCultureIgnoreCase))
+                    {
+                        // Check to see if the element is repeating and needs to be an array
+                        if (repeatingElements.Contains(propertyElement.Name.LocalName))
+                        {
+                            // See if property was already created as an array, if not then create it
+                            if (!repeatingObjectArrays.ContainsKey(propertyName))
+                            {
+                                repeatingObjectArrays.Add(propertyName, new List<JsonObject>());
+                            }
+                            repeatingObjectArrays[propertyName].Add(BuildElementJsonObject(propertyElement));
+                        }
+                        else
+                        {
+                            jsonObject[propertyName] = BuildElementJsonObject(propertyElement);
+                        }
+                    }
+                    else
+                    {
+                        extensions.Add(BuildExtensionsElementObject(propertyElement));
+                    }
+                }
+
+                if (extensions.Count > 0)
+                {
+                    jsonObject["extensions"] = extensions.ToArray();
+                }
+
+                foreach (string key in repeatingObjectArrays.Keys)
+                {
+                    jsonObject[key] = repeatingObjectArrays[key].ToArray();
+                }
+            }
+            else
+            {
+                jsonObject[MakeFirstLetterLowercase(container.Name.LocalName)] = container.Value;
+            }
+
+            return jsonObject;
+        }
+
+        /// <summary>
+        /// Makes the first letter of a string lowercase
+        /// </summary>
+        /// <param name="name">The string to be modified</param>
+        /// <returns>Modified string</returns>
+        private static string MakeFirstLetterLowercase(string str)
+        {
+            if (!string.IsNullOrWhiteSpace(str))
+            {
+                if (str.Length > 1 && !(str[1].ToString() == str[1].ToString().ToUpper()))
+                {
+                    return str[0].ToString().ToLower() + str.Substring(1);
+                }
+                else
+                {
+                    return str;
+                }
+            }
+
+            return str;
+        }
+    }
+}
\ No newline at end of file
diff --git a/JSLib/tests/code/jsdate.cs b/JSLib/tests/code/jsdate.cs
new file mode 100644
index 0000000..40996d0
--- /dev/null
+++ b/JSLib/tests/code/jsdate.cs
@@ -0,0 +1,40 @@
+/// <summary>
+/// The oracle's representation of a Javascript date object as deserialized by the library
+/// </summary>
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Linq;
+    using System.Net;
+    using System.Runtime.Serialization;
+    using System.ServiceModel;
+    using System.ServiceModel.Activation;
+    using System.ServiceModel.Syndication;
+    using System.ServiceModel.Web;
+    using System.Xml;
+    using System.Xml.Linq;
+    using System.Spatial;
+    using Microsoft.Data.OData;
+
+    [Serializable]
+    public class JsDate : JsonObject
+    {
+        private static readonly DateTime JsEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+        public JsDate(DateTime dateTime)
+            : base()
+        {
+            this["milliseconds"] = dateTime.Subtract(JsEpoch).TotalMilliseconds;
+        }
+
+        public JsDate(DateTimeOffset dateTimeOffset)
+            : this(dateTimeOffset.UtcDateTime)
+        {
+            this["__edmType"] = "Edm.DateTimeOffset";
+            this["__offset"] = (dateTimeOffset.Offset < TimeSpan.Zero ? "-" : "+") + dateTimeOffset.Offset.ToString("hh':'mm");
+        }
+    }
+}
\ No newline at end of file
diff --git a/JSLib/tests/code/jsonlightreader.cs b/JSLib/tests/code/jsonlightreader.cs
new file mode 100644
index 0000000..183027f
--- /dev/null
+++ b/JSLib/tests/code/jsonlightreader.cs
@@ -0,0 +1,313 @@
+/// <summary>
+/// Class used to parse the Content section of the feed to return the properties data and metadata
+/// </summary>
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Net;
+    using System.Web.Script.Serialization;
+    using System.Xml;
+    using Microsoft.Data.Edm;
+    using Microsoft.Data.Edm.Csdl;
+
+    public static class JsonLightReader
+    {
+        private static Dictionary<string, string> nameMap = new Dictionary<string, string>() {
+            {"readLink", "self"},
+            {"editLink", "edit"},
+            {"nextLink", "__next"},
+            {"mediaReadLink", "media_src"},
+            {"mediaEditLink", "edit_media"},
+            {"mediaContentType", "content_type"},
+            {"mediaETag", "media_etag"},
+            {"count", "__count"},
+            {"media_src", "mediaReadLink"},
+            {"edit_media", "mediaEditLink"},
+            {"content_type", "mediaContentType"},
+            {"media_etag", "mediaETag"},
+            {"url", "uri"}
+        };
+
+        public static JsonObject ReadJsonLight(TextReader payload)
+        {
+            var serializer = new JavaScriptSerializer();
+            serializer.RegisterConverters(new JavaScriptConverter[] { new JsonObjectConverter() });
+
+            var json = serializer.Deserialize<JsonObject>(payload.ReadToEnd());
+
+            IEdmModel model = null;
+            string metadataFragment = null;
+            string metadataURI = json["odata.metadata"] as string;
+
+            if (metadataURI != null)
+            {
+                int fragmentStart = metadataURI.IndexOf("#");
+                string metadataUrl = metadataURI;
+
+                if (fragmentStart > 0)
+                {
+                    metadataUrl = metadataURI.Substring(0, fragmentStart);
+                    metadataFragment = metadataURI.Substring(fragmentStart + 1);
+                }
+                model = GetEndpointModel(metadataUrl);
+            }
+        }
+
+        private static IEdmModel GetEndpointModel(string modelUrl)
+        {
+            using (WebResponse response = ReaderUtils.CreateRequest(modelUrl).GetResponse())
+            {
+                return EdmxReader.Parse(XmlReader.Create(response.GetResponseStream()));
+            }
+        }
+
+        private static JsonObject ReadObject(JsonObject jsonLight, IEdmModel model)
+        {
+            var json = new JsonObject();
+            var metadata = json["__metadata"] = new JsonObject();
+
+            foreach (var item in jsonLight)
+            {
+                string name = item.Key;
+                if (name.IndexOf(".", StringComparison.OrdinalIgnoreCase) == -1)
+                {
+                    if (item.Value is JsonObject)
+                    {
+                        json[item.Key] = ReadObject((JsonObject)item.Value, model);
+                    }
+                }
+            }
+
+            return json;
+        }
+
+        private static JsonObject ReadObjectProperties(IEnumerable<KeyValuePair<string, object>> properties, JsonObject json)
+        {
+            return json;
+        }
+
+        private static JsonObject ApplyPayloadAnnotationsToObject(IEnumerable<KeyValuePair<string, object>> annotations, JsonObject json)
+        {
+            foreach (var item in annotations)
+            {
+                ApplyPayloadAnnotationToObject(item.Key, item.Value, json);
+            }
+            return json;
+        }
+
+        private static JsonObject ApplyPayloadAnnotationToObject(string annotation, object value, JsonObject json)
+        {
+            int index = annotation.IndexOf("@", StringComparison.OrdinalIgnoreCase);
+            string target = null;
+            string name = annotation;
+
+            if (index > 0)
+            {
+                target = annotation.Substring(0, index);
+                name = annotation.Substring(index + 1);
+            }
+
+            if (name.StartsWith("odata.", StringComparison.Ordinal))
+            {
+                return ApplyODataPayloadAnnotation(name, target, value, json);
+            }
+
+            json["annotation"] = value;
+            return json;
+        }
+
+        private static JsonObject ApplyODataPayloadAnnotation(string annotation, string target, string targetType, object value, Uri baseUri, JsonObject json)
+        {
+            string name = annotation.Substring("odata.".Length);
+            switch (name)
+            {
+                case "navigationLinkUrl":
+                    return ApplyNavigationUrlAnnotation(name, target, targetType, value, baseUri, json);
+                case "nextLink":
+                case "count":
+                    return ApplyFeedAnnotation(name, target, value, baseUri, json);
+                case "mediaReadLink":
+                case "mediaEditLink":
+                case "mediaContentType":
+                case "mediaETag":
+                    return ApplyMediaAnnotation(name, target, targetType, value, baseUri, json);
+                default:
+                    return ApplyMetadataAnnotation(name, target, targetType, value, baseUri, json);
+            }
+        }
+
+        private static JsonObject ApplyNavigationUrlAnnotation(string name, string target, string targetType, object value, Uri baseUri, JsonObject json)
+        {
+            JsonObject propertiesMetadata = GetOrCreatePropertiesMetadata(json);
+            JsonObject propertyMetadata = GetOrCreateObjectProperty(propertiesMetadata, target);
+
+            string uri = NormalizeUri((string)value, baseUri);
+
+            if (json.ContainsKey(target))
+            {
+                propertyMetadata["navigationLinkUrl"] = uri;
+            }
+            else
+            {
+                JsonObject navProp = new JsonObject();
+                JsonObject deferred = new JsonObject();
+
+                deferred["uri"] = uri;
+                navProp["__deferred"] = deferred;
+                json[target] = navProp;
+
+                if (!propertyMetadata.ContainsKey("type"))
+                {
+                    propertyMetadata["type"] = targetType;
+                }
+            }
+            return json;
+        }
+
+        private static JsonObject ApplyFeedAnnotation(string name, string target, object value, Uri baseUri, JsonObject json)
+        {
+            string mappedName = MapODataName(name);
+            JsonObject feed = (target == null) ? json : (JsonObject)json[target];
+            feed[mappedName] = (name == "nextLink") ? NormalizeUri((string)value, baseUri) : value;
+            return json;
+        }
+
+        private static JsonObject ApplyMediaAnnotation(string name, string target, string targetType, object value, Uri baseUri, JsonObject json)
+        {
+            string mappedName = MapODataName(name);
+            object theValue = value;
+
+            if (name == "mediaReadLink" || name == "mediaEditLink")
+            {
+                theValue = NormalizeUri((string)value, baseUri);
+            }
+
+            if (target != null)
+            {
+                JsonObject propertiesMetadata = GetOrCreatePropertiesMetadata(json);
+                JsonObject propertyMetadata = GetOrCreateObjectProperty(propertiesMetadata, target);
+                JsonObject namedStream = GetOrCreateObjectProperty(json, target);
+                JsonObject mediaResource = GetOrCreateObjectProperty(namedStream, "__mediaresource");
+
+                if (!propertyMetadata.ContainsKey("type") || propertyMetadata["type"] == null)
+                {
+                    propertyMetadata["type"] = targetType;
+                }
+                mediaResource[mappedName] = theValue;
+            }
+            else
+            {
+                JsonObject metadata = GetOrCreateObjectMetadata(json);
+                metadata[mappedName] = value;
+            }
+            return json;
+        }
+
+        private static JsonObject ApplyMetadataAnnotation(string name, string target, string targetType, object value, Uri baseUri, JsonObject json)
+        {
+            string mappedName = MapODataName(name);
+            JsonObject metadata = GetOrCreateObjectMetadata(json);
+
+            if (name == "editLink")
+            {
+                metadata["uri"] = NormalizeUri((string)value, baseUri);
+                metadata[mappedName] = metadata["uri"];
+                return json;
+            }
+
+            if (name == "readLink" || name == "associationLinkUrl")
+            {
+                metadata[mappedName] = NormalizeUri((string)value, baseUri);
+                return json;
+            }
+
+            if (target != null)
+            {
+                JsonObject propertiesMetadata = GetOrCreatePropertiesMetadata(json);
+                JsonObject propertyMetadata = GetOrCreateObjectProperty(propertiesMetadata, target);
+
+                if (name == "type")
+                {
+                    if (!propertyMetadata.ContainsKey("type") || propertyMetadata["type"] == null)
+                    {
+                        propertyMetadata["type"] = targetType;
+                        return json;
+                    }
+                }
+                propertyMetadata[mappedName] = value;
+                return json;
+            }
+            metadata[mappedName] = value;
+            return json;
+        }
+
+        private static string MapODataName(string name)
+        {
+            return nameMap.ContainsKey(name) ? nameMap[name] : name;
+        }
+
+        private static JsonObject GetOrCreateObjectProperty(JsonObject json, string name)
+        {
+            if (!json.ContainsKey(name))
+            {
+                json[name] = new JsonObject();
+            }
+            return (JsonObject)json[name];
+        }
+
+        private static JsonObject GetOrCreateObjectMetadata(JsonObject json)
+        {
+            return GetOrCreateObjectProperty(json, "__metadata");
+        }
+
+        private static JsonObject GetOrCreatePropertiesMetadata(JsonObject json)
+        {
+            JsonObject metadata = GetOrCreateObjectMetadata(json);
+            return GetOrCreateObjectProperty(metadata, "properties");
+        }
+
+        private static string NormalizeUri(string uri, Uri baseUri)
+        {
+            Uri tmpUri = new Uri(uri, UriKind.RelativeOrAbsolute);
+
+            if (!tmpUri.IsAbsoluteUri && baseUri != null)
+            {
+                tmpUri = new Uri(baseUri, tmpUri);
+                return tmpUri.AbsoluteUri;
+            }
+            return tmpUri.OriginalString;
+        }
+
+        private class JsonObjectConverter : JavaScriptConverter
+        {
+            public override object Deserialize(IDictionary<string, object> dictionary, System.Type type, JavaScriptSerializer serializer)
+            {
+                var json = new JsonObject();
+                foreach (var item in dictionary)
+                {
+                    object value = item.Value;
+                    if (value is IDictionary<string, object>)
+                    {
+                        value = serializer.ConvertToType<JsonObject>(value);
+                    }
+                    json[item.Key] = value;
+                }
+                return json;
+            }
+
+            public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
+            {
+                throw new System.NotImplementedException();
+            }
+
+            public override IEnumerable<Type> SupportedTypes
+            {
+                get { return new Type[] { typeof(JsonObject) }; }
+            }
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/JSLib/tests/code/jsonobject.cs b/JSLib/tests/code/jsonobject.cs
new file mode 100644
index 0000000..79b914e
--- /dev/null
+++ b/JSLib/tests/code/jsonobject.cs
@@ -0,0 +1,80 @@
+/// <summary>
+/// A weakly typed representation of a JSON object using a dictionary implementation
+/// </summary>
+/// <typeparam name="T">The CLR type of the values of the properties</typeparam>
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Runtime.Serialization;
+
+    [Serializable]
+    [KnownType(typeof(JsonObject))]
+    [KnownType(typeof(JsonObject[]))]
+    [KnownType(typeof(JsDate))]
+    [KnownType(typeof(List<object>))]
+    public class JsonObject : ISerializable, IEnumerable<KeyValuePair<string, object>>
+    {
+        Dictionary<string, object> dictionary = new Dictionary<string, object>();
+
+        public void Remove(string key)
+        {
+            dictionary.Remove(key);
+        }
+
+        public object this[string key]
+        {
+            get
+            {
+                return this.dictionary[key];
+            }
+            set
+            {
+                this.dictionary[key] = value;
+            }
+        }
+
+        public bool ContainsKey(string key)
+        {
+            return this.dictionary.ContainsKey(key);
+        }
+
+        public static JsonObject Merge(JsonObject first, JsonObject second)
+        {
+            if (first == null)
+            {
+                return second;
+            }
+
+            if (second != null)
+            {
+                JsonObject merged = new JsonObject();
+                merged.dictionary = new Dictionary<string, object>(first.dictionary);
+                foreach (var pair in second.dictionary)
+                {
+                    merged.dictionary[pair.Key] = pair.Value;
+                }
+                return merged;
+            }
+            return first;
+        }
+
+        public void GetObjectData(SerializationInfo info, StreamingContext context)
+        {
+            this.dictionary.ToList().ForEach(pair => info.AddValue(pair.Key, pair.Value));
+        }
+
+        public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+        {
+            return this.dictionary.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return this.dictionary.GetEnumerator();
+        }
+    }
+}
\ No newline at end of file
diff --git a/JSLib/tests/code/readerutils.cs b/JSLib/tests/code/readerutils.cs
new file mode 100644
index 0000000..35b98ba
--- /dev/null
+++ b/JSLib/tests/code/readerutils.cs
@@ -0,0 +1,48 @@
+using System.Net;
+namespace DataJS.Tests
+{
+    public static class ReaderUtils
+    {
+        public static JsonObject CreateEntryPropertyMetadata(string type)
+        {
+            return CreateEntryPropertyMetadata(type, true);
+        }
+
+        public static JsonObject CreateEntryPropertyMetadata(string type, bool withExtensions)
+        {
+            JsonObject json = new JsonObject();
+            json["type"] = type;
+
+
+            // TODO: add proper support for property extensions
+            if (withExtensions)
+            {
+                json["extensions"] = new JsonObject[] { };
+            }
+
+            return json;
+        }
+
+        public static JsonObject CreateExtension(string name, string nameSpace, string value)
+        {
+            JsonObject json = new JsonObject();
+            json["name"] = name;
+            json["namespaceURI"] = nameSpace;
+            json["value"] = value;
+            return json;
+        }
+
+        public static WebRequest CreateRequest(string url, string user = null, string password = null)
+        {
+            WebRequest request = WebRequest.Create(url);
+            if (user != null || password != null)
+            {
+                request.Credentials = new NetworkCredential(user, password);
+                request.PreAuthenticate = true;
+            }
+
+            return request;
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/JSLib/tests/common/ODataReadOracle.js b/JSLib/tests/common/ODataReadOracle.js
new file mode 100644
index 0000000..a0c0bbc
--- /dev/null
+++ b/JSLib/tests/common/ODataReadOracle.js
@@ -0,0 +1,275 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// Client for the odata.read oracle service
+
+(function (window, undefined) {
+    var jsonMime = "application/json;odata=verbose";
+    var universalMime = "*/*";
+    var atomMime = "application/atom+xml";
+
+    var readFeed = function (url, success, mimeType, recognizeDates) {
+        /// <summary>Calls the ReadFeed endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the feed from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        /// <param name="mimeType" type="String">The MIME media type in the Accept header</param>
+        var readMethod = getReadMethod(mimeType, "ReadFeed");
+        oracleRequest("GET", readMethod, typeof url === "string" ? { url: url} : url, mimeType, recognizeDates, function (data) {
+            if (!data.results) {
+                data = { results: data };
+            }
+            success(data);
+        });
+    };
+
+    var readEntry = function (url, success, mimeType, recognizeDates) {
+        /// <summary>Calls the ReadEntry endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the entry from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        /// <param name="mimeType" type="String">The MIME media type in the Accept header</param>
+        var readMethod = getReadMethod(mimeType, "ReadEntry");
+        oracleRequest("GET", readMethod, typeof url === "string" ? { url: url} : url, mimeType, recognizeDates, success);
+    };
+
+    var readFeedLoopback = function (atomFeedXml, success, recognizeDates) {
+        /// <summary>Calls the ReadFeedLoopback endpoint with the specified atom feed xml</summary>
+        /// <param name="atomFeedXml" type="String">The atom feed xml</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        oracleRequest("POST", "ReadFeedLoopback", atomFeedXml, atomMime, recognizeDates, success);
+    };
+
+    var readEntryLoopback = function (atomEntryXml, success, recognizeDates) {
+        /// <summary>Calls the ReadEntryLoopback endpoint with the specified atom entry xml</summary>
+        /// <param name="atomEntryXml" type="String">The atom entry xml</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        oracleRequest("POST", "ReadEntryLoopback", atomEntryXml, atomMime, recognizeDates, success);
+    };
+
+    var readLinksEntry = function (url, success) {
+        /// <summary>Calls the ReadMetadata endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the metadata from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        readJson(
+            url,
+            success
+        );
+    };
+
+    var readLinksFeed = function (url, success) {
+        /// <summary>Calls the ReadMetadata endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the metadata from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        readJson(
+            url,
+            function (data) {
+                if (!data.results) {
+                    data = { results: data };
+                }
+                success(data);
+            }
+        );
+    };
+
+    var readMetadata = function (url, success) {
+        /// <summary>Calls the ReadMetadata endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the metadata from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        $.getJSON(
+            "./common/ODataReadOracle.svc/ReadMetadata?url=" + escape(url),
+            function (data) {
+                removeProperty(data.d, "__type");
+                success(data.d);
+            }
+        );
+    };
+
+    var readServiceDocument = function (url, success, mimeType) {
+        /// <summary>Calls the ReadServiceDocument endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to the service</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        /// <param name="mimeType" type="String">The MIME type being tested</param>
+
+        $.getJSON(
+            "./common/ODataReadOracle.svc/ReadServiceDocument?url=" + escape(url) + "&mimeType=" + mimeType,
+            function (data) {
+                removeProperty(data.d, "__type");
+                if (mimeType == jsonMime) {
+                    removeProperty(data.d, "extensions");
+                    $.each(data.d["workspaces"], function (_, workspace) {
+                        delete workspace["title"];
+                    });
+                }
+                success(data.d);
+            }
+        );
+    };
+
+    var readJson = function (url, success) {
+        $.ajax({
+            url: url,
+            accepts: null,
+            dataType: "json",
+            beforeSend: function (xhr) {
+                xhr.setRequestHeader("Accept", jsonMime);
+                xhr.setRequestHeader("MaxDataServiceVersion", "3.0");
+            },
+            success: function (data) {
+                success(data.d);
+            }
+        });
+    };
+
+    var readJsonAcrossServerPages = function (url, success) {
+        var data = [];
+        var readPage = function (url) {
+            readJson(url, function (feedData) {
+                var results = feedData.results || feedData;
+                var next = feedData.__next;
+
+                data = data.concat(results);
+                if (next) {
+                    readPage(next);
+                } else {
+                    success(data);
+                }
+            });
+        };
+
+        readPage(url);
+    }
+
+    var getReadMethod = function (mimeType, defaultEndpoint) {
+        switch (mimeType) {
+            case universalMime:
+            case atomMime:
+                return defaultEndpoint;
+            case jsonMime:
+            default:
+                return "ReadJson";
+        }
+    }
+
+    var oracleRequest = function (method, endpoint, data, mimeType, recognizeDates, success) {
+        /// <summary>Requests a JSON object from the oracle service, removing WCF-specific artifacts</summary>
+        /// <param name="method" type="String">The HTTP method (GET or POST)</param>
+        /// <param name="endpoint" type="String">The oracle endpoint</param>
+        /// <param name="data" type="Object">The data to send with the request</param>
+        /// <param name="reviver" type="Function">The reviver function to run on each deserialized object</param>
+        /// <param name="success" type="Function">Success callback</param>
+        var reviver = mimeType === jsonMime || mimeType === undefined ? (recognizeDates ? odataDateReviver : undefined) : oracleDateReviver;
+        var url = "./common/ODataReadOracle.svc/" + endpoint;
+        $.ajax({
+            type: method,
+            url: url,
+            data: data,
+            dataType: "text",
+            success: function (data) {
+                var json = JSON.parse(data, reviver);
+                removeProperty(json.d, "__type");
+                success(json.d);
+            }
+        });
+    };
+
+    var removeProperty = function (data, property) {
+        /// <summary>Removes the specified property recursively from the given object</summary>
+        /// <param name="data" type="Object">The object to operate on</param>
+        /// <param name="property" type="String">The name of the property to remove</param>
+        if (typeof data === "object" && data !== null) {
+            if (data[property]) {
+                delete data[property];
+            }
+
+            for (prop in data) {
+                removeProperty(data[prop], property);
+            }
+        }
+    }
+
+    var oracleDateReviver = function (key, value) {
+        /// <summary>Revives date objects received from the oracle service</summary>
+        if (value && value["__type"] && value["__type"].search("JsDate") > -1) {
+            var data = new Date(value.milliseconds);
+            if (value["__edmType"]) {
+                data["__edmType"] = value["__edmType"];
+            }
+
+            if (value["__offset"]) {
+                data["__offset"] = value["__offset"];
+            }
+
+            return data;
+        }
+
+        return value;
+    }
+
+    var odataDateReviver = function (key, value) {
+        /// <summary>Revives date objects received from OData JSON payloads</summary>
+        var regexp = /^\/Date\((-?\d+)(\+|-)?(\d+)?\)\/$/;
+        var matches = regexp.exec(value);
+        if (matches) {
+            var milliseconds = parseInt(matches[1], 10);
+            if (!isNaN(milliseconds)) {
+                var result = new Date(milliseconds);
+                if (matches[2]) {
+                    var sign = matches[2];
+                    var offsetMinutes = parseInt(matches[3], 10);
+                    if (sign === "-") {
+                        offsetMinutes = -offsetMinutes;
+                    }
+
+                    result.setUTCMinutes(result.getUTCMinutes() - offsetMinutes);
+                    result["__edmType"] = "Edm.DateTimeOffset";
+                    result["__offset"] = minutesToOffset(offsetMinutes);
+                }
+                return result;
+            }
+        }
+
+        return value;
+    }
+
+    var minutesToOffset = function (minutes) {
+        var padIfNeeded = function (value) {
+            var result = value.toString(10);
+            return result.length < 2 ? "0" + result : result;
+        };
+
+        var sign;
+        if (minutes < 0) {
+            sign = "-";
+            minutes = -minutes;
+        } else {
+            sign = "+";
+        }
+
+        var hours = Math.floor(minutes / 60);
+        minutes = minutes - (60 * hours);
+
+        return sign + padIfNeeded(hours) + ":" + padIfNeeded(minutes);
+    };
+
+    window.ODataReadOracle = {
+        readFeed: readFeed,
+        readEntry: readEntry,
+        readFeedLoopback: readFeedLoopback,
+        readEntryLoopback: readEntryLoopback,
+        readLinksEntry: readLinksEntry,
+        readLinksFeed: readLinksFeed,
+        readJson: readJson,
+        readJsonAcrossServerPages: readJsonAcrossServerPages,
+        readMetadata: readMetadata,
+        readServiceDocument: readServiceDocument
+    };
+})(window);
\ No newline at end of file
diff --git a/JSLib/tests/common/ODataReadOracle.svc b/JSLib/tests/common/ODataReadOracle.svc
new file mode 100644
index 0000000..32b4d50
--- /dev/null
+++ b/JSLib/tests/common/ODataReadOracle.svc
@@ -0,0 +1,182 @@
+<%@ ServiceHost Language="C#" Debug="true" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"
+    Service="DataJS.Tests.ODataReadOracle" %>
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+//uncomment this line to debug JSON serialization.
+//#define DEBUG_SERIALIZATION
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Linq;
+    using System.Net;
+    using System.Runtime.Serialization;
+    using System.ServiceModel;
+    using System.ServiceModel.Activation;
+    using System.ServiceModel.Syndication;
+    using System.ServiceModel.Web;
+    using System.Xml;
+    using System.Xml.Linq;
+    using System.Spatial;
+    using Microsoft.Data.OData;
+
+    /// <summary>
+    /// Oracle for the OData.read library function
+    /// </summary>
+    [ServiceContract]
+    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
+    public class ODataReadOracle
+    {
+        const string jsonVerboseMediaType = "application/json;odata=verbose";
+
+        /// <summary>
+        /// Reads a URI that will return an OData ATOM feed
+        /// </summary>
+        /// <param name="url">The URL to send the request to</param>
+        /// <param name="user">The username for basic authentication</param>
+        /// <param name="password">The password for basic authentication</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadFeed(string url, string user, string password)
+        {
+            WebResponse response = ReaderUtils.CreateRequest(ResolveUri(url, UriKind.Absolute), user, password).GetResponse();
+            return AtomReader.ReadFeed(new StreamReader(response.GetResponseStream()));
+        }
+
+        /// <summary>
+        /// Reads a URI that will return an OData ATOM feed entry
+        /// </summary>
+        /// <param name="url">URL of the entry</param>
+        /// <param name="user">The username for basic authentication</param>
+        /// <param name="password">The password for basic authentication</param>
+        /// <returns>JSON object expected to be returned by OData.read</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadEntry(string url, string user, string password)
+        {
+            WebResponse response = ReaderUtils.CreateRequest(ResolveUri(url, UriKind.Absolute), user, password).GetResponse();
+            return AtomReader.ReadEntry(new StreamReader(response.GetResponseStream()));
+        }
+
+        /// <summary>
+        /// Reads a URI that will return a metadata object
+        /// </summary>
+        /// <param name="url">The URL to send the request to</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadMetadata(string url)
+        {
+            WebResponse response = WebRequest.Create(ResolveUri(url, UriKind.Absolute)).GetResponse();
+            return CsdlReader.ReadCsdl(new StreamReader(response.GetResponseStream()));
+        }
+
+        /// <summary>
+        /// Reads a URI that will return a metadata object
+        /// </summary>
+        /// <param name="url">The URL to send the request to</param>
+        /// <param name="mimeType">Mime type being tested to determine base URI</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadServiceDocument(string url, string mimeType)
+        {
+            WebResponse response = WebRequest.Create(ResolveUri(url, UriKind.Absolute)).GetResponse();
+            string baseUri = string.Empty;
+
+            // With JSON responses only relative path passed to the library is available
+            if (mimeType.Equals(jsonVerboseMediaType))
+            {
+                baseUri = ResolveUri(url, UriKind.Relative).ToString();
+            }
+            else
+            {
+                baseUri = response.ResponseUri.AbsoluteUri;
+            }
+
+            return AtomReader.ReadServiceDocument(new StreamReader(response.GetResponseStream()), baseUri);
+        }
+
+        /// <summary>
+        /// Reads a URI that will get the Json response and return the stream
+        /// </summary>
+        /// <param name="url">URL of the entry</param>
+        /// <param name="user">The username for basic authentication</param>
+        /// <param name="password">The password for basic authentication</param>
+        /// <returns>Stream of the Json response expected to be returned by OData.read</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public Stream ReadJson(string url, string user, string password)
+        {
+            HttpWebRequest request = (HttpWebRequest)ReaderUtils.CreateRequest(ResolveUri(url, UriKind.Absolute), user, password);
+            request.Accept = jsonVerboseMediaType + "; charset=utf-8";
+            WebResponse response = request.GetResponse();
+
+            return response.GetResponseStream();
+        }
+
+
+        /// <summary>
+        /// Loops back an ATOM feed passed to the webservice in JSON format.
+        /// </summary>
+        /// <param name="content">The ATOM feed xml stream to loopback as JSON</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadFeedLoopback(Stream content)
+        {
+            return AtomReader.ReadFeed(new StreamReader(content));
+        }
+
+        /// <summary>
+        /// Loops back an ATOM entry passed to the webservice in JSON format.
+        /// </summary>
+        /// <param name="content">The ATOM entry xml stream to loopback as JSON</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadEntryLoopback(Stream content)
+        {
+            return AtomReader.ReadEntry(new StreamReader(content));
+        }
+
+        /// <summary>
+        /// Resolves the given url string to a URI
+        /// </summary>
+        /// <param name="url">The given URL string</param>
+        /// <param name="urlKind">URI kind to resolve to</param>
+        /// <returns>The resolved URI</returns>
+        private static string ResolveUri(string url, UriKind uriKind)
+        {
+            Uri resolvedUri = new Uri(url, UriKind.RelativeOrAbsolute);
+            if (!resolvedUri.IsAbsoluteUri)
+            {
+                // If the given URI is relative, then base it on the Referer URI
+                Uri baseUri = new Uri(WebOperationContext.Current.IncomingRequest.Headers["Referer"]);
+                resolvedUri = new Uri(baseUri, resolvedUri);
+                if (uriKind == UriKind.Relative)
+                {
+                    resolvedUri = baseUri.MakeRelativeUri(resolvedUri);
+                }
+            }
+
+            return resolvedUri.ToString();
+        }
+    }
+}
\ No newline at end of file
diff --git a/JSLib/tests/common/TestLogger.svc b/JSLib/tests/common/TestLogger.svc
new file mode 100644
index 0000000..d236b24
--- /dev/null
+++ b/JSLib/tests/common/TestLogger.svc
@@ -0,0 +1,846 @@
+<%@ ServiceHost Language="C#" Debug="true" Factory="DataJS.Tests.TestSynchronizerFactory" Service="DataJS.Tests.TestSynchronizer" %>
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Net;
+    using System.ServiceModel;
+    using System.ServiceModel.Activation;
+    using System.ServiceModel.Channels;
+    using System.ServiceModel.Description;
+    using System.ServiceModel.Dispatcher;
+    using System.ServiceModel.Web;
+    using System.Text;
+    using System.Threading;
+    using System.Xml;
+    
+    /// <summary>
+    /// This factory supports reconfiguring the service to allow incoming messages
+    /// to be larger than the default.
+    /// </summary>
+    public class TestSynchronizerFactory : WebScriptServiceHostFactory
+    {
+        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
+        {
+            var result = base.CreateServiceHost(serviceType, baseAddresses);
+            result.Opening += ServiceHostOpening;
+            return result;
+        }
+
+        private static void UpdateService(ServiceDescription description)
+        {
+            const long LargeMaxReceivedMessageSize = 1024 * 1024 * 16;
+            foreach (var endpoint in description.Endpoints)
+            {
+                var basic = endpoint.Binding as BasicHttpBinding;
+                if (basic != null)
+                {
+                    basic.MaxReceivedMessageSize = LargeMaxReceivedMessageSize;
+                }
+
+                var http = endpoint.Binding as WebHttpBinding;
+                if (http != null)
+                {
+                    http.MaxReceivedMessageSize = LargeMaxReceivedMessageSize;
+                }
+            }
+        }
+
+        private void ServiceHostOpening(object sender, EventArgs e)
+        {
+            UpdateService((sender as ServiceHost).Description);
+        }        
+    }
+
+    /// <summary>Use this class to log test activity.</summary>
+    /// <remarks>
+    /// A test run can be created by invoking CreateTestRun. With a test
+    /// run ID, the following operations can be invoked:
+    ///
+    /// - AddTestPages: adds test pages to be made available to future callers (typically to support tests from different files)
+    /// - SetTestNamePrefix: sets a string that will be prefixed to every test name in logs (typically to include a browser name)
+    /// - MarkInProgress: resets the test run to "in-progress" for another variation (typically to run a different browser)
+    /// - IsTestRunInProgress: checks whether it's still in progress
+    /// - GetTestRunResults: returns the test results in TRX format
+    /// - LogAssert: logs a single assertion in the current test for the run
+    /// - LogTestStart: logs a test that has begun execution
+    /// - LogTestDone: logs a test that has ended execution
+    /// - TestCompleted: logs that a test has completed execution; returns the next page with tests or an empty string
+    /// </remarks>
+    [ServiceContract]
+    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
+    public class TestSynchronizer
+    {
+        private static readonly Dictionary<string, TestRunContext> testRuns = new Dictionary<string, TestRunContext>();
+        private const string Inconclusive = "Inconclusive";
+        private const string InProgress = "InProgress";
+        private const string Failed = "Failed";
+        private const string Passed = "Passed";
+        private const string Completed = "Completed";
+        
+        /// <summary>
+        /// Adds test pages to the specified runs; replaces existing files (helps with reliablity when something
+        /// fails part-way).
+        /// </summary>
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public int AddTestPages(string testRunId, string pages, string filter)
+        {
+            DisableResponseCaching();
+            
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                context.TestPages.Clear();
+                context.TestPages.AddRange(pages.Split(',').Select(page => page + "?testRunId=" + testRunId + (filter == null ? string.Empty : "?filter=" + filter)));
+                return context.TestPages.Count;
+            }
+        }
+
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public string CreateTestRun()
+        {
+            DisableResponseCaching();
+
+            Guid value = Guid.NewGuid();
+            string result = value.ToString();
+            TestRunContext context = CreateTestRunContextWithId(value);
+            
+            lock (testRuns)
+            {
+                testRuns.Add(result, context);
+            }
+
+            return result;
+        }
+
+        /// <summary>Checks whether the test run is in progress.</summary>
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public bool IsTestRunInProgress(string testRunId)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            return context.TestRun.ResultSummary.Outcome == InProgress;
+        }
+        
+        /// <summary>Provides a list of all test runs being tracked.</summary>
+        [OperationContract]
+        [WebGet(ResponseFormat=WebMessageFormat.Json)]
+        public IEnumerable<string> GetActiveTestRuns()
+        {
+            DisableResponseCaching();
+
+            List<string> result;
+            lock (testRuns)
+            {
+                result = new List<string>(testRuns.Keys);
+            }
+            
+            return result;
+        }
+
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Xml)]
+        public Message GetTestRunResults(string testRunId)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                TestRun run = context.TestRun;
+                this.CompleteTestRun(run);
+
+                TestRunXmlBodyWriter writer = new TestRunXmlBodyWriter(run);
+                return Message.CreateMessage(
+                    MessageVersion.None,
+                    OperationContext.Current.OutgoingMessageHeaders.Action,
+                    writer);
+            }
+        }
+
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void LogAssert(string testRunId, bool pass, string message, string name, string actual, string expected)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                TestRun run = context.TestRun;
+                string prefixedName = context.TestNamePrefix + name;
+                TestResult result = run.TestResults.LastOrDefault(r => r.TestName == prefixedName);
+                if (result == null)
+                {
+                    throw new InvalidOperationException("Unable to find test " + prefixedName + " in run " + testRunId);
+                }
+                
+                result.DebugTrace.AppendLine(message);
+                if (!pass)
+                {
+                    result.ErrorMessages.AppendLine(message);
+                    result.ErrorMessages.AppendLine("Expected: " + expected);
+                    result.ErrorMessages.AppendLine("Actual: " + actual);
+                }
+            }
+        }
+        
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebInvoke(ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
+        public void LogBatch(string[] urls)
+        {
+            DisableResponseCaching();
+            
+            foreach (var url in urls)
+            {
+                Uri parsed = new Uri(OperationContext.Current.Channel.LocalAddress.Uri, url);
+                string methodName = parsed.Segments[parsed.Segments.Length - 1];
+                System.Reflection.MethodInfo method = this.GetType().GetMethod(methodName);
+                System.Reflection.ParameterInfo[] parameterInfos = method.GetParameters();
+                object[] parameters = new object[parameterInfos.Length];
+                System.Collections.Specialized.NameValueCollection query = System.Web.HttpUtility.ParseQueryString(parsed.Query);
+                for (int i = 0; i < parameters.Length; i++)
+                {
+                    object value = query[parameterInfos[i].Name];
+                    parameters[i] = Convert.ChangeType(value, parameterInfos[i].ParameterType, System.Globalization.CultureInfo.InvariantCulture);
+                }
+                
+                method.Invoke(this, parameters);
+            }            
+        }
+
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void LogTestStart(string testRunId, string name, DateTime startTime)
+        {
+            DisableResponseCaching();
+            
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                TestRun run = context.TestRun;
+                string prefixedName = context.TestNamePrefix + name;
+                Guid testId = Guid.NewGuid();
+                Guid executionId = Guid.NewGuid();
+                Guid testListId = run.TestLists.Single().Id;
+                run.TestDefinitions.Add(new TestDefinition()
+                {
+                    Id = testId,
+                    Name = prefixedName,
+                    ExecutionId = executionId,
+                });
+                run.TestEntries.Add(new TestEntry()
+                {
+                    TestId = testId,
+                    ExecutionId = executionId,
+                    TestListId = testListId
+                });
+                run.TestResults.Add(new TestResult()
+                {
+                    ExecutionId = executionId,
+                    TestId = testId,
+                    TestListId = testListId,
+                    TestName = prefixedName,
+                    ComputerName = Environment.MachineName,
+                    StartTime = startTime,
+                    EndTime = startTime,
+                    TestType = Guid.Parse("13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b"),
+                    Outcome = InProgress,
+                    // RelativeResultsDirectory?
+                });
+            }
+        }
+
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void LogTestDone(string testRunId, string name, int failures, int total, DateTime endTime)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                TestRun run = context.TestRun;
+                string prefixedName = context.TestNamePrefix + name;
+                TestResult result = run.TestResults.LastOrDefault(r => r.TestName == prefixedName);
+                if (failures > 0)
+                {
+                    result.Outcome = Failed;
+                }
+                else
+                {
+                    result.Outcome = Passed;
+                }
+
+                result.EndTime = endTime;
+            }
+        }
+
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void MarkInProgress(string testRunId)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                context.TestRun.ResultSummary.Outcome = InProgress;
+            }
+        }
+
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void SetTestNamePrefix(string testRunId, string prefix)
+        {
+            DisableResponseCaching();
+            
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                context.TestNamePrefix = prefix;
+            }
+        }
+
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public string TestCompleted(string testRunId, int failures, int total)
+        {
+            DisableResponseCaching();
+            
+            var context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                string result;
+                if (context.TestPages.Count == 0)
+                {
+                    context.TestRun.ResultSummary.Outcome = Completed;
+                    result = "";
+                }
+                else
+                {
+                    result = context.TestPages[0];
+                    context.TestPages.RemoveAt(0);
+                }
+
+                return result;
+            }
+        }
+
+        private static TestRunContext CreateTestRunContextWithId(Guid value)
+        {
+            TestRun run = new TestRun();
+            run.Id = value;
+            run.Name = "Test run";
+            run.TestTimes.Creation = DateTime.Now;
+            run.TestTimes.Queueing = DateTime.Now;
+            run.TestLists.Add(new TestList()
+            {
+                Name = "All Results",
+                Id = Guid.NewGuid()
+            });
+
+            // For the time being, set up a fake test settings.
+            run.TestSettings.Id = Guid.NewGuid();
+
+            run.ResultSummary.Outcome = InProgress;
+
+            TestRunContext context = new TestRunContext();
+            context.TestRun = run;
+
+            return context;
+        }
+
+        private static void DisableResponseCaching()
+        {
+            WebOperationContext.Current.OutgoingResponse.Headers[HttpResponseHeader.CacheControl] = "no-cache";            
+        }
+
+        private static TestRunContext GetTestRunContext(string testRunId)
+        {
+            if (testRunId == null)
+            {
+                throw new ArgumentNullException("testRunId");
+            }
+            
+            lock (testRuns)
+            {
+                // For an 0-filled GUID, allow create-on-demand to simplify ad-hoc testing.
+                // Something like:
+                // http://localhost:8989/tests/odata-qunit-tests.htm?testRunId=00000000-0000-0000-0000-000000000000
+                if (!testRuns.ContainsKey(testRunId))
+                {
+                    Guid value = Guid.Parse(testRunId);
+                    if (value == Guid.Empty)
+                    {
+                        TestRunContext context = CreateTestRunContextWithId(value);
+                        testRuns.Add(testRunId, context);
+                    }
+                }
+                
+                return testRuns[testRunId];
+            }
+        }
+
+        private void CompleteTestRun(TestRun run)
+        {
+            run.TestTimes.Finish = DateTime.Now;
+
+            // Fill counts in result object.
+            var summary = run.ResultSummary;
+            summary.Executed = 0;
+            summary.Error = 0;
+            summary.Failed = 0;
+            summary.Timeout = 0;
+            summary.Aborted = 0;
+            summary.Inconclusive = 0;
+            summary.PassedButRunAborted = 0;
+            summary.NotRunnable = 0;
+            summary.NotExecuted = 0;
+            summary.Disconnected = 0;
+            summary.Warning = 0;
+            summary.Passed = 0;
+            summary.Completed = 0;
+            summary.InProgress = 0;
+            summary.Pending = 0;
+
+            foreach (var testResult in run.TestResults)
+            {
+                string outcome = testResult.Outcome;
+                switch (outcome)
+                {
+                    case InProgress:
+                        summary.Executed++;
+                        summary.InProgress++;
+                        break;
+                    case Failed:
+                        summary.Executed++;
+                        summary.Completed++;
+                        summary.Failed++;
+                        break;
+                    case Passed:
+                        summary.Executed++;
+                        summary.Completed++;
+                        summary.Passed++;
+                        break;
+                    default:
+                        summary.Failed++;
+                        break;
+                }
+            }
+
+            summary.Total = run.TestResults.Count;
+
+            if (summary.Failed != 0)
+            {
+                summary.Outcome = Failed;
+            }
+            else if (summary.Total <= 0 || summary.Passed < summary.Total)
+            {
+                summary.Outcome = Inconclusive;
+            }
+            else
+            {
+                summary.Outcome = Passed;
+            }
+        }
+    }
+
+    public class TestRunContext
+    {
+        public TestRunContext()
+        {
+            this.TestPages = new List<string>();
+        }
+        
+        public TestRun TestRun { get; set; }
+        public string TestNamePrefix { get; set; }
+        public List<string> TestPages { get; set; }
+    }
+
+    public class TestResultWriter
+    {
+        private const string TestNamespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010";
+
+        public static void WriteTestRun(TestRun run, string path)
+        {
+            if (run == null)
+            {
+                throw new ArgumentNullException("run");
+            }
+            if (path == null)
+            {
+                throw new ArgumentNullException("path");
+            }
+
+            using (XmlWriter writer = XmlWriter.Create(path))
+            {
+                WriteTestRun(run, path);
+            }
+        }
+
+        public static void WriteTestRun(TestRun run, XmlWriter writer)
+        {
+            if (run == null)
+            {
+                throw new ArgumentNullException("run");
+            }
+            if (writer == null)
+            {
+                throw new ArgumentNullException("writer");
+            }
+
+            writer.WriteStartElement("TestRun", TestNamespace);
+            writer.WriteGuidIfPresent("id", run.Id);
+            writer.WriteAttributeString("name", run.Name);
+            writer.WriteAttributeString("runUser", run.RunUser);
+
+            WriteTestSettings(run.TestSettings, writer);
+            WriteResultSummary(run.ResultSummary, writer);
+
+            // Write test definitions.
+            writer.WriteStartElement("TestDefinitions", TestNamespace);
+            foreach (var definition in run.TestDefinitions)
+            {
+                WriteTestDefinition(definition, writer);
+            }
+
+            writer.WriteEndElement();
+
+            // Write test lists.
+            writer.WriteStartElement("TestLists", TestNamespace);
+            foreach (var list in run.TestLists)
+            {
+                WriteTestList(list, writer);
+            }
+
+            writer.WriteEndElement();
+
+            // Write test entries.
+            writer.WriteStartElement("TestEntries", TestNamespace);
+            foreach (var entry in run.TestEntries)
+            {
+                WriteTestEntry(entry, writer);
+            }
+
+            writer.WriteEndElement();
+
+            // Write test results.
+            writer.WriteStartElement("Results", TestNamespace);
+            foreach (var result in run.TestResults)
+            {
+                WriteTestResults(result, writer);
+            }
+
+            writer.WriteEndElement();
+
+            // Close the test run element.
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestResults(TestResult result, XmlWriter writer)
+        {
+            if (result == null)
+            {
+                throw new ArgumentNullException("result");
+            }
+
+            writer.WriteStartElement("UnitTestResult", TestNamespace);
+            writer.WriteGuidIfPresent("testId", result.TestId);
+            writer.WriteGuidIfPresent("testListId", result.TestListId);
+            writer.WriteGuidIfPresent("executionId", result.ExecutionId);
+            writer.WriteGuidIfPresent("RelativeResultsDirectory", result.RelativeResultsDirectory);
+
+            writer.WriteAttributeString("testName", result.TestName);
+            writer.WriteAttributeString("computerName", result.ComputerName);
+            writer.WriteAttributeString("duration", result.Duration.ToString());
+            writer.WriteAttributeString("startTime", XmlConvert.ToString(result.StartTime));
+            writer.WriteAttributeString("endTime", XmlConvert.ToString(result.EndTime));
+            writer.WriteAttributeString("outcome", result.Outcome);
+
+            writer.WriteGuidIfPresent("testType", result.TestType);
+
+            if (result.DebugTrace.Length > 0)
+            {
+                writer.WriteStartElement("Output");
+
+                writer.WriteStartElement("DebugTrace");
+                writer.WriteString(result.DebugTrace.ToString());
+                writer.WriteEndElement();
+
+                writer.WriteStartElement("ErrorInfo");
+                writer.WriteStartElement("Message");
+                writer.WriteString(result.ErrorMessages.ToString());
+                writer.WriteEndElement();
+                writer.WriteEndElement();
+
+                writer.WriteEndElement();
+            }
+            
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestEntry(TestEntry entry, XmlWriter writer)
+        {
+            if (entry == null)
+            {
+                throw new ArgumentNullException("entry");
+            }
+
+            writer.WriteStartElement("TestEntry", TestNamespace);
+            writer.WriteGuidIfPresent("testId", entry.TestId);
+            writer.WriteGuidIfPresent("testListId", entry.TestListId);
+            writer.WriteGuidIfPresent("executionId", entry.ExecutionId);
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestList(TestList list, XmlWriter writer)
+        {
+            if (list == null)
+            {
+                throw new ArgumentNullException("list");
+            }
+
+            writer.WriteStartElement("TestList", TestNamespace);
+            writer.WriteAttributeString("name", list.Name);
+            writer.WriteGuidIfPresent("id", list.Id);
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestDefinition(TestDefinition definition, XmlWriter writer)
+        {
+            if (definition == null)
+            {
+                throw new ArgumentNullException("definition");
+            }
+
+            writer.WriteStartElement("UnitTest", TestNamespace);
+            writer.WriteAttributeString("name", definition.Name);
+            writer.WriteAttributeString("storage", definition.Storage);
+            writer.WriteGuidIfPresent("id", definition.Id);
+            
+            // There are more thing we could write here: DeploymentItems, Execution, TestMethod
+            
+            // This is the minimum needed to load the test in the IDE.
+            writer.WriteStartElement("Execution", TestNamespace);
+            writer.WriteGuidIfPresent("id", definition.ExecutionId);
+            writer.WriteEndElement();
+
+            writer.WriteStartElement("TestMethod", TestNamespace);
+            writer.WriteAttributeString("codeBase", "fake-test-file.js");
+            writer.WriteAttributeString("adapterTypeName", "Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestAdapter, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.Adapter, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
+            writer.WriteAttributeString("className", "FakeClassName, TestLogging, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
+            writer.WriteAttributeString("name", definition.Name);
+            writer.WriteEndElement();
+            
+            writer.WriteEndElement();
+        }
+
+        private static void WriteResultSummary(ResultSummary resultSummary, XmlWriter writer)
+        {
+            if (resultSummary == null)
+            {
+                throw new ArgumentNullException("resultSummary");
+            }
+
+            writer.WriteStartElement("ResultSummary", TestNamespace);
+            writer.WriteAttributeString("outcome", resultSummary.Outcome);
+
+            writer.WriteStartElement("Counters", TestNamespace);
+            
+            foreach (var p in typeof(ResultSummary).GetProperties())
+            {
+                if (p.PropertyType != typeof(int))
+                {
+                    continue;
+                }
+
+                int value = (int)p.GetValue(resultSummary, null);
+                string attributeName = p.Name;
+                attributeName = attributeName.Substring(0, 1).ToLowerInvariant() +  attributeName.Substring(1);
+                writer.WriteAttributeString(attributeName, value.ToString());
+            }
+            
+            writer.WriteEndElement();
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestSettings(TestSettings testSettings, XmlWriter writer)
+        {
+            if (testSettings == null)
+            {
+                throw new ArgumentNullException("testSettings");
+            }
+            
+            writer.WriteStartElement("TestSettings", TestNamespace);
+            writer.WriteAttributeString("name", testSettings.Name);
+            writer.WriteGuidIfPresent("id", testSettings.Id);
+            // There are more things we could write here.
+            writer.WriteEndElement();
+        }
+    }
+
+    public static class XmlWriterExtensions
+    {
+        public static void WriteGuidIfPresent(this XmlWriter writer, string attributeName, Guid value)
+        {
+            if (value != Guid.Empty)
+            {
+                writer.WriteAttributeString(attributeName, value.ToString());
+            }
+        }
+    }
+
+    public class TestRun
+    {
+        public TestRun()
+        {
+            this.TestDefinitions = new List<TestDefinition>();
+            this.TestLists = new List<TestList>();
+            this.TestEntries = new List<TestEntry>();
+            this.TestResults = new List<TestResult>();
+            this.ResultSummary = new ResultSummary();
+            this.TestTimes = new TestTimes();
+            this.TestSettings = new TestSettings();
+            
+            this.Id = Guid.NewGuid();
+            this.RunUser = Environment.UserDomainName + "\\" + Environment.UserName;
+        }
+
+        public Guid Id { get; set; }
+        public string Name { get; set; }
+        public string RunUser { get; set; }
+        public TestSettings TestSettings { get; set; }
+        public TestTimes TestTimes { get; set; }
+        public ResultSummary ResultSummary { get; set; }
+        public List<TestDefinition> TestDefinitions { get; set; }
+        public List<TestList> TestLists { get; set; }
+        public List<TestEntry> TestEntries { get; set; }
+        public List<TestResult> TestResults { get; set; }
+    }
+
+    public class TestSettings
+    {
+        public Guid Id { get; set; }
+        public string Name { get; set; }
+    }
+
+    public class TestTimes
+    {
+        public DateTime Creation { get; set; }
+        public DateTime Queueing { get; set; }
+        public DateTime Start { get; set; }
+        public DateTime Finish { get; set; }
+    }
+
+    public class ResultSummary
+    {
+        public string Outcome { get; set; }
+        public int Total { get; set; }
+        public int Executed { get; set; }
+        public int Error { get; set; }
+        public int Failed { get; set; }
+        public int Timeout { get; set; }
+        public int Aborted { get; set; }
+        public int Inconclusive { get; set; }
+        public int PassedButRunAborted { get; set; }
+        public int NotRunnable { get; set; }
+        public int NotExecuted { get; set; }
+        public int Disconnected { get; set; }
+        public int Warning { get; set; }
+        public int Passed { get; set; }
+        public int Completed { get; set; }
+        public int InProgress { get; set; }
+        public int Pending { get; set; }
+    }
+
+    public class TestDefinition
+    {
+        public string Name { get; set; }
+        public string Storage { get; set; }
+        public Guid Id { get; set; }
+        public Guid ExecutionId { get; set; }
+    }
+
+    public class TestList
+    {
+        public string Name { get; set; }
+        public Guid Id { get; set; }
+    }
+
+    public class TestEntry
+    {
+        public Guid TestId { get; set; }
+        public Guid ExecutionId { get; set; }
+        public Guid TestListId { get; set; }
+    }
+
+    public class TestResult
+    {
+        public TestResult()
+        {
+            this.DebugTrace = new StringBuilder();
+            this.ErrorMessages = new StringBuilder();
+        }
+        
+        public Guid ExecutionId { get; set; }
+        public Guid TestId { get; set; }
+        public string TestName { get; set; }
+        public string ComputerName { get; set; }
+        public TimeSpan Duration { get { return this.EndTime - this.StartTime; } }
+        public DateTime StartTime { get; set; }
+        public DateTime EndTime { get; set; }
+        public Guid TestType { get; set; }
+        public string Outcome { get; set; }
+        public Guid TestListId { get; set; }
+        public Guid RelativeResultsDirectory { get; set; }
+        public StringBuilder DebugTrace { get; set; }
+        public StringBuilder ErrorMessages { get; set; }
+    }
+
+    class TestRunXmlBodyWriter : BodyWriter
+    {
+        private readonly TestRun run;
+
+        public TestRunXmlBodyWriter(TestRun run)
+            : base(true)
+        {
+            this.run = run;
+        }
+
+        protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
+        {
+            TestResultWriter.WriteTestRun(this.run, writer);
+        }
+    }
+}
\ No newline at end of file
diff --git a/JSLib/tests/common/TestSynchronizerClient.js b/JSLib/tests/common/TestSynchronizerClient.js
new file mode 100644
index 0000000..c758413
--- /dev/null
+++ b/JSLib/tests/common/TestSynchronizerClient.js
@@ -0,0 +1,218 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// TestSynchronizer Client
+// Use to log assert pass/fails and notify mstest a test has completed execution
+
+(function (window, undefined) {
+    var testRunId = "";
+    var serviceRoot = "./common/TestLogger.svc/";
+    var recording = null;
+    var recordingLength = 0;
+    var maxStringLength = 8192;
+    var maxPostLength = 2097152;
+
+    var callTestSynchronizer = function (methodName, parameterUrl) {
+        /// <summary>Invokes a function on the test synchronizer.</summary>
+        /// <param name="partialUrl" type="String" optional="true">URL to work with.</param>
+        /// <returns type="String">A response from the server, possibly null.</returns>
+        /// <remarks>
+        /// If the recording variable is assigned, then the call is logged
+        /// but nothing is invoked.
+        /// </remarks>
+
+        var partialUrl;
+        if (testRunId) {
+            partialUrl = methodName + "?testRunId=" + testRunId + "&" + parameterUrl;
+        }
+        else {
+            partialUrl = methodName + "?" + parameterUrl;
+        }
+
+        var url = serviceRoot + partialUrl;
+
+        if (recording) {
+            if (url.length > maxStringLength) {
+                url = url.substr(0, maxStringLength);
+            }
+
+            recordingLength += url.length;
+            if (recordingLength > maxPostLength) {
+                submitRecording();
+                recording = [];
+                recordingLength = url.length;
+            }
+
+            recording.push(url);
+            return null;
+        }
+
+        var xhr;
+        if (window.XMLHttpRequest) {
+            xhr = new window.XMLHttpRequest();
+        } else {
+            xhr = new ActiveXObject("Msxml2.XMLHTTP.6.0");
+        }
+
+        xhr.open("GET", url, false);
+        xhr.send();
+        return xhr.responseText;
+    };
+
+    var getLogPrefix = function (result) {
+        /// <summary>Returns the log prefix for a given result</summary>
+        /// <param name="result" type="Boolean">Whether the result is pass or fail. If null, the log line is assumed to be diagnostic</param>
+        return "[" + getShortDate() + "] " + (result === true ? "[PASS] " : (result === false ? "[FAIL] " : ""));
+    };
+
+    var getShortDate = function () {
+        /// <summary>Returns the current date and time formatted as "yyyy-mm-dd hh:mm:ss.nnn".</summary>
+        var padToLength = function (number, length) {
+            var result = number + "";
+            var lengthDiff = length - result.length;
+            for (var i = 0; i < lengthDiff; i++) {
+                result = "0" + result;
+            }
+
+            return result;
+        }
+
+        var date = new Date();
+        var day = padToLength(date.getDate(), 2);
+        var month = padToLength(date.getMonth() + 1, 2);
+        var year = date.getFullYear();
+
+        var hours = padToLength(date.getHours(), 2);
+        var minutes = padToLength(date.getMinutes(), 2);
+        var seconds = padToLength(date.getSeconds(), 2);
+        var milliseconds = padToLength(date.getMilliseconds(), 3);
+
+        return year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds + "." + milliseconds;
+    };
+
+    var submitRecording = function () {
+        var body = { urls: recording };
+        postToUrl("LogBatch", body);
+    };
+
+    var postToUrl = function (methodName, body) {
+        /// <summary>POSTs body to the designated methodName.</summary>
+        var xhr;
+        if (window.XMLHttpRequest) {
+            xhr = new window.XMLHttpRequest();
+        } else {
+            xhr = new ActiveXObject("Msxml2.XMLHTTP.6.0");
+        }
+
+        var url = serviceRoot + methodName;
+        xhr.open("POST", url, false);
+        xhr.setRequestHeader("Content-Type", "application/json");
+        xhr.send(window.JSON.stringify(body));
+        if (xhr.status < 200 || xhr.status > 299) {
+            throw { message: "Unable to POST to url.\r\n" + xhr.responseText };
+        }
+
+        return xhr.responseText;
+    }
+
+    function LogAssert(result, message, name, expected, actual) {
+        var parameterUrl = "pass=" + result + "&message=" + encodeURIComponent(message) + "&name=" + encodeURIComponent(name);
+
+        if (!result) {
+            parameterUrl += "&actual=" + encodeURIComponent(actual) + "&expected=" + encodeURIComponent(expected);
+        }
+
+        callTestSynchronizer("LogAssert", parameterUrl);
+    }
+
+    function LogTestStart(name) {
+        callTestSynchronizer("LogTestStart", "name=" + encodeURIComponent(name) + "&startTime=" + encodeURIComponent(getShortDate()));
+    }
+
+    function LogTestDone(name, failures, total) {
+        callTestSynchronizer("LogTestDone", "name=" + encodeURIComponent(name) + "&failures=" + failures + "&total=" + total + "&endTime=" + encodeURIComponent(getShortDate()));
+    }
+
+    function TestCompleted(failures, total) {
+        return callTestSynchronizer("TestCompleted", "failures=" + failures + "&total=" + total);
+    }
+
+    var extractTestRunId = function () {
+        /// <summary>Extracts the testRunId value from the window query string.</summary>
+        /// <returns type="String">testRunId, possibly empty.</returns>
+        var i, len;
+        var uri = window.location.search;
+        if (uri) {
+            var parameters = uri.split("&");
+            for (i = 0, len = parameters.length; i < len; i++) {
+                var index = parameters[i].indexOf("testRunId=");
+                if (index >= 0) {
+                    return parameters[i].substring(index + "testRunId=".length);
+                }
+            }
+        }
+
+        return "";
+    };
+
+    var init = function (qunit) {
+        /// <summary>Initializes the test logger synchronizer.</summary>
+        /// <param name="qunit">Unit testing to hook into.</param>
+        /// <remarks>If there is no testRunId present, the QUnit functions are left as they are.</remarks>
+        var logToConsole = function (context) {
+            if (window.console && window.console.log) {
+                window.console.log(context.result + ' :: ' + context.message);
+            }
+        };
+
+        testRunId = extractTestRunId();
+        if (!testRunId) {
+            qunit.log = logToConsole;
+        } else {
+            recording = [];
+            qunit.log = function (context) {
+                logToConsole(context);
+
+                var name = qunit.config.current.testName;
+                if (!(context.actual && context.expected)) {
+                    context.actual = context.result;
+                    context.expected = true;
+                }
+                LogAssert(context.result, getLogPrefix(context.result) + context.message, name, window.JSON.stringify(context.expected), window.JSON.stringify(context.actual));
+            };
+
+            qunit.testStart = function (context) {
+                LogTestStart(context.name);
+            };
+
+            qunit.testDone = function (context) {
+                LogTestDone(context.name, context.failed, context.total);
+            }
+
+            qunit.done = function (context) {
+                submitRecording();
+                recording = null;
+
+                var nextUrl = TestCompleted(context.failed, context.total);
+                nextUrl = JSON.parse(nextUrl).d;
+                if (nextUrl) {
+                    window.location.href = nextUrl;
+                }
+            }
+        }
+    };
+
+    window.TestSynchronizer = {
+        init: init
+    };
+})(window);
\ No newline at end of file
diff --git a/JSLib/tests/common/cacheoracle.js b/JSLib/tests/common/cacheoracle.js
new file mode 100644
index 0000000..59a695b
--- /dev/null
+++ b/JSLib/tests/common/cacheoracle.js
@@ -0,0 +1,119 @@
+// Copyright (c) Microsoft.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// CacheOracle.js
+// This object verifies the operation of the cache.
+// Internally it maintains a simple model of the cache implemented using a lookup array of the expected cached pages.
+
+(function (window, undefined) {
+
+    var CacheOracle = function (baseUri, pageSize, total) {
+        /// <summary>Creates a new CacheOracle</summary>
+        /// <param name="baseUri" type="String">The base URI of the collection</param>
+        /// <param name="pageSize" type="Integer">The page size used in the cache</param>
+        /// <param name="total" type="Integer">The total number of items in the collection</param>
+        this.baseUri = baseUri;
+        this.pageSize = pageSize;
+        this.total = total;
+
+        this.cachedPages = [];
+    };
+
+    CacheOracle.mechanisms = {
+        memory: "memory",
+        indexeddb: "indexeddb",
+        dom: "dom",
+        best: "best"
+    };
+
+    CacheOracle.isMechanismAvailable = function (mechanism) {
+        /// <summary>Determines if the specified local storage mechanism is available</summary>
+        /// <param name="mechanism">The name of the mechanism</param>
+        /// <returns>Whether the mechanism is available</returns>
+        switch (mechanism) {
+            case CacheOracle.mechanisms.indexeddb:
+                if (window.mozIndexedDB) {
+                    return true;
+                }
+                else {
+                    return false;
+                }
+                break;
+            case CacheOracle.mechanisms.dom:
+                if (window.localStorage) {
+                    return true;
+                }
+                else {
+                    return false;
+                }
+                break;
+            case CacheOracle.mechanisms.memory:
+            case CacheOracle.mechanisms.best:
+            case undefined:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    CacheOracle.prototype.clear = function () {
+        /// <summary>Clears the cache in the oracle</summary>
+        this.cachedPages = [];
+    }
+
+    CacheOracle.prototype.verifyRequests = function (requests, responses, index, count, description) {
+        /// <summary>Verifies the HTTP requests for a single data request, and updates the oracle with cached pages</summary>
+        /// <param name="requests" type="Array">The sequence of request objects (from OData.defaultHttpClient)</param>
+        /// <param name="responses" type="Array">The sequence of response objects (from OData.defaultHttpClient)</param>
+        /// <param name="index" type="Integer">The starting index of the read</param>
+        /// <param name="count" type="Integer">The count of items in the read</param>
+        /// <param name="description" type="String">The description of the requests being verified</param>
+        var that = this;
+
+        var pageIndex = function (index) {
+            /// <summary>Returns the page index that the given item index belongs to</summary>
+            /// <param name="index" type="Integer">The item index</param>
+            /// <returns>The page index</returns>
+            return Math.floor(index / that.pageSize);
+        }
+
+        var minPage = pageIndex(index);
+        var maxPage = Math.min(pageIndex(index + count - 1), pageIndex(that.total));
+
+        // Workaround for Bug 2055: Calling readRange with count = 0 still fires a single HTTP request
+        maxPage = Math.max(minPage, maxPage);
+
+        var expectedUris = [];
+        var responseIndex = 0;
+        for (var page = minPage; page <= maxPage; page++) {
+            if (!this.cachedPages[page]) {
+                expectedUris.push(that.baseUri + "?$skip=" + page * that.pageSize + "&$top=" + (that.pageSize));
+
+                // Handle server paging skipToken requests
+                while (responses[responseIndex] && responses[responseIndex].data && responses[responseIndex].data.__next) {
+                    expectedUris.push(responses[responseIndex].data.__next);
+                    responseIndex++;
+                }
+
+                responseIndex++;
+                this.cachedPages[page] = true;
+            }
+        }
+
+        var actualUris = $.map(requests, function (r) { return r.requestUri; });
+        djstest.assertAreEqualDeep(actualUris, expectedUris, description);
+    };
+
+    window.CacheOracle = CacheOracle;
+
+})(this);
\ No newline at end of file
diff --git a/JSLib/tests/common/djstest.js b/JSLib/tests/common/djstest.js
new file mode 100644
index 0000000..c7b2de5
--- /dev/null
+++ b/JSLib/tests/common/djstest.js
@@ -0,0 +1,400 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+(function (window, undefined) {
+    var djstest = {};
+
+    window.djstest = djstest;
+
+    djstest.indexedDB = window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.indexedDB;
+
+    djstest.cleanStoreOnIndexedDb = function (storeObjects, done) {
+        /// <summary>Cleans all the test data saved in the IndexedDb database.</summary>
+        /// <param name="storeNames" type="Array">Array of store objects with a property that is the name of the store</param>
+        /// <param name="done" type="Function">Callback function</param>
+
+        var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || {};
+
+        var deleteObjectStores = function (db) {
+            $.each(db.objectStoreNames, function (_, storeName) {
+                db.deleteObjectStore(storeName);
+            });
+        };
+
+        if (djstest.indexedDB) {
+            var job = new djstest.Job();
+            $.each(storeObjects, function (_, storeObject) {
+                job.queue((function (storeObject) {
+                    return function (success, fail) {
+                        var dbname = "_datajs_" + storeObject.name;
+                        var request = djstest.indexedDB.open(dbname);
+                        request.onsuccess = function (event) {
+                            var db = request.result;
+
+                            if ("setVersion" in db) {
+                                var versionRequest = db.setVersion("0.1");
+                                versionRequest.onsuccess = function (event) {
+                                    var transaction = versionRequest.transaction;
+                                    transaction.oncomplete = function () {
+                                        db.close();
+                                        success();
+                                    }
+                                    deleteObjectStores(db);
+                                };
+                                versionRequest.onerror = function (e) {
+                                    djstest.fail("Error on cleanup - code: " + e.code + " name: " + e.name + "message: " + message);
+                                    fail();
+                                };
+                                return;
+                            }
+
+                            // new api cleanup
+                            db.close();
+                            var deleteRequest = djstest.indexedDB.deleteDatabase(dbname);
+                            deleteRequest.onsuccess = function (event) {
+                                djstest.log("djstest indexeddb cleanup - deleted database " + dbname);
+                                success();
+                            };
+                            deleteRequest.onerror = function (e) {
+                                djstest.fail("djstest indexeddb cleanup - error deleting database " + dbname);
+                                fail();
+                            };
+                            djstest.log("djstest indexeddb cleanup - requested deletion of database " + dbname);
+                        };
+
+                        request.onerror = function (e) {
+                            djstest.fail(e.code + ": " + e.message);
+                        };
+                    };
+                })(storeObject));
+            });
+        }
+
+        if (job) {
+            job.run(function (succeeded) {
+                if (!succeeded) {
+                    djstest.fail("cleanup job failed");
+                }
+                done();
+            });
+        }
+        else {
+            done();
+        }
+    };
+
+    djstest.Job = function () {
+        /// <summary>Constructs a Job object that allows for enqueuing and synchronizing the execution of functions.</summary>
+        /// <returns type="Object">Job object</returns>
+        var currentTask = -1;
+        var tasks = [];
+
+        var failedTasks = 0;
+
+        this.queue = function (fn) {
+            /// <summary>Adds a function to the job queue regardless if the queue is already executing or not.</summary>
+            /// <param name="fn" type="Function">Function to execute.</param>
+            tasks.push(fn);
+        };
+
+        this.queueNext = function (fn) {
+            /// <summary>Adds a function to the front of the job queue regardless if the queue is already executing or not.</summary>
+            /// <param name="fn" type="Function">Function to execute.</param>
+            if (currentTask < 0) {
+                tasks.unshift(fn);
+            } else {
+                tasks.splice(currentTask + 1, 0, fn);
+            }
+        };
+
+        this.run = function (done) {
+            /// <summary>Starts the execution of this job.</summary>
+            /// <param name="done" type="Function">Callback invoked when the job has finished executing all of its enqueued tasks.</param>
+            /// <remarks>
+            /// This method does nothing if called on a unit of work that is already executing.
+            /// </remarks>
+
+            if (currentTask >= 0) {
+                return;
+            }
+
+            if (tasks.length === 0) {
+                done(true);
+                return;
+            }
+
+            var makeTaskDoneCallBack = function (failed) {
+                return function () {
+                    // Track the failed task and continue the execution of the job. 
+                    if (failed) {
+                        failedTasks++;
+                    }
+                    currentTask++;
+                    if (currentTask === tasks.length) {
+                        done(failedTasks === 0);
+                    } else {
+                        runNextTask();
+                    }
+                };
+            };
+
+            var runNextTask = function () {
+                /// <summary>Executes the next function in the queue.</summary>
+                defer(function () {
+                    try {
+                        tasks[currentTask](makeTaskDoneCallBack(false), makeTaskDoneCallBack(true));
+                    } catch (e) {
+                        makeTaskDoneCallBack(true)();
+                    }
+                });
+            };
+
+            currentTask = 0;
+            runNextTask();
+        };
+    };
+
+    var defer = function (fn) {
+        /// <summary>Defers the execution of an arbitrary function that takes no parameters.</summary>
+        /// <param name="fn" type="Function">Function to schedule for later execution.</param>
+        setTimeout(fn, 0);
+    }
+
+    var exposeDateValues = function (data) {
+        /// <summary>Exposes date values for Date objects to facilitate debugging</summary>
+        /// <param name="data" type="Object">The object to operate on</param>
+        if (typeof data === "object") {
+            if (data instanceof Date) {
+                data["__date__"] = data.toUTCString();
+            }
+            else {
+                for (var prop in data) {
+                    exposeDateValues(data[prop]);
+                }
+            }
+        }
+
+        return data;
+    }
+
+    var extractFunctionName = function (text) {
+        /// <summary>Determines the name of a function.</summary>
+        /// <param name="text" type="String">Function text.</param>
+        /// <returns type="String">The name of the function from text if found; the original text otherwise.</returns>
+
+        var index = text.indexOf("function ");
+        if (index < 0) {
+            return text;
+        }
+
+        var nameStart = index + "function ".length;
+        var parensIndex = text.indexOf("(", nameStart);
+        if (parensIndex < 0) {
+            return text;
+        }
+
+        var result = text.substr(nameStart, parensIndex - nameStart);
+        if (result.indexOf("test") === 0) {
+            result = result.substr("test".length);
+        }
+
+        return result;
+    };
+
+    var removeMetadata = function (data) {
+        /// <summary>Removes metadata annotations from the specified object.</summary>
+        /// <param name="data">Object to remove metadata from; possibly null.</param>
+
+        if (typeof data === "object" && data !== null) {
+            delete data["__metadata"];
+            for (prop in data) {
+                removeMetadata(data[prop]);
+            }
+        }
+    };
+
+    djstest.addTest = function (fn, name, arg, timeout) {
+        if (!name) {
+            name = extractFunctionName(fn.toString());
+        }
+
+        test(name, function () {
+            if (!timeout) {
+                timeout = 20000;
+            }
+
+            QUnit.config.testTimeout = timeout;
+            QUnit.stop();
+            fn.call(this, arg);
+        });
+    }
+
+    djstest.assert = function (test, message) {
+        /// <summary>Asserts that a condition is true.</summary>
+        /// <param name="test" type="Boolean">Condition to test.</param>
+        /// <param name="message" type="String">Text message for condition being tested.</param>
+        QUnit.ok(test, message);
+    };
+
+    djstest.assertAreEqual = function (actual, expected, message) {
+        /// <summary>Asserts that the values of the expected and actualobjects are equal.</summary>
+        QUnit.equal(actual, expected, message);
+    };
+
+    djstest.assertAreEqualDeep = function (actual, expected, message) {
+        /// <summary>Asserts that the actual and expected objects are the same.</summary>
+        QUnit.deepEqual(exposeDateValues(actual), exposeDateValues(expected), message);
+    };
+
+    djstest.assertWithoutMetadata = function (actual, expected, message) {
+        removeMetadata(actual)
+        removeMetadata(expected);
+        djstest.assertAreEqualDeep(actual, expected, message);
+    };
+
+    djstest.asyncDo = function (asyncActions, done) {
+        /// <summary>Calls each async action in asyncActions, passing each action a function which keeps a count and
+        /// calls the passed done function when all async actions complete.</summary>
+        /// <param name="asyncActions" type="Array">Array of asynchronous actions to be executed, 
+        /// each taking a single parameter - the callback function to call when the action is done.</param>
+        /// <param name="done" type="Function">Function to be executed in the last async action to complete.</param>
+        var count = 0;
+        var doneOne = function () {
+            count++;
+            if (count >= asyncActions.length) {
+                done();
+            }
+        };
+
+        if (asyncActions.length > 0) {
+            $.each(asyncActions, function (_, asyncAction) {
+                asyncAction(doneOne);
+            });
+        } else {
+            done();
+        }
+    }
+
+    djstest.clone = function (object) {
+        /// <summary>Makes a deep copy of an object.</summary>
+        return $.extend(true, {}, object);
+    };
+
+    djstest.destroyCacheAndDone = function (cache) {
+        /// <summary>Destroys the cache and then completes the test</summary>
+        /// <param name="cache">The cache to destroy</param>
+        cache.clear().then(function () {
+            djstest.done();
+        }, function (err) {
+            djstest.fail("Failed to destroy cache: " + djstest.toString(err));
+            djstest.done();
+        });
+    };
+
+    djstest.done = function () {
+        /// <summary>Indicates that the currently running test has finished.</summary>
+        QUnit.start();
+    };
+
+    djstest.expectException = function (testFunction, message) {
+        /// <summary>Test passes if and only if an exception is thrown.</summary>
+        try {
+            testFunction();
+            djstest.fail("Expected exception but function succeeded: " + " " + message);
+        }
+        catch (e) {
+            // Swallow exception.
+            djstest.pass("Thrown exception expected");
+        }
+    };
+
+    djstest.assertsExpected = function (asserts) {
+        /// <summary>Indicates the expected number of asserts, fails test if number is not met.</summary>
+        /// <param name="asserts" type="Number">Number of asserts expected in test.</param>
+        expect(asserts);
+    }
+
+    djstest.fail = function (message) {
+        /// <summary>Marks the current test as failed.</summary>
+        /// <param name="message" type="String">Failure message.</param>
+        QUnit.ok(false, message);
+    };
+
+    djstest.failAndDoneCallback = function (message, cleanupCallback) {
+        /// <summary>Returns a function that when invoked will fail this test and be done with it.</summary>
+        /// <param name="message" type="String">Failure message.</param>
+        /// <param name="cleanupCallback" type="Function" optional="true">Optional cleanup function in case of failure.</param>
+        /// <returns type="Function">A new function.</returns>
+
+        return function (err) {
+            message = "" + message + (err) ? window.JSON.stringify(err) : "";
+            djstest.fail(message);
+            if (cleanupCallback) {
+                try {
+                    cleanupCallback();
+                } catch (e) {
+                    djstest.fail("error during cleanupCallback: " + window.JSON.stringify(e));
+                }
+            }
+
+            djstest.done();
+        };
+    };
+
+    djstest.log = function (message) {
+        /// <summary>Logs a test message.</summary>
+        /// <param name="message" type="String">Test message.</param>
+        var context = { result: true, actual: true, expected: true, message: message };
+        QUnit.log(context);
+    };
+
+    djstest.pass = function (message) {
+        /// <summary>Marks the current test as failed.</summary>
+        /// <param name="message" type="String">Failure message.</param>
+        QUnit.ok(true, message);
+    };
+
+    djstest.toString = function (obj) {
+        /// <summary>Dumps the object as a string</summary>
+        /// <param name="obj" type="Object">Object to dump</param>
+        return QUnit.jsDump.parse(obj);
+    };
+
+    djstest.wait = function (fn) {
+        /// <summary>Executes the function, pausing test execution until the callback is called</summary>
+        /// <param name="fn" type="Function">Function to execute; takes one parameter which is the callback</param>
+        /// <remarks>This function is typically used in asynchronous setup/teardown methods</remarks>
+        QUnit.stop();
+        fn(function () {
+            QUnit.start();
+        });
+    };
+
+    // Disable caching to ensure that every test-related AJAX request is actually being sent,
+    // and set up a default error handler
+    $.ajaxSetup({
+        cache: false,
+        error: function (jqXHR, textStatus, errorThrown) {
+            // Work around bug in IE-Mobile on Windows Phone 7
+            if (jqXHR.status !== 1223) {
+                var err = {
+                    status: jqXHR.status,
+                    statusText: jqXHR.statusText,
+                    responseText: jqXHR.responseText
+                };
+                djstest.fail("AJAX request failed with: " + djstest.toString(err));
+            }
+            djstest.done();
+        }
+    });
+})(window);
\ No newline at end of file
diff --git a/JSLib/tests/common/observablehttpclient.js b/JSLib/tests/common/observablehttpclient.js
new file mode 100644
index 0000000..c624814
--- /dev/null
+++ b/JSLib/tests/common/observablehttpclient.js
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// ObservableHttpClient.js
+// This object extends OData's default httpClient by supporting request and response recording sessions, and firing a custom
+// JQuery event for each request/response.
+//
+// The events fired by this object are:
+//      request: Before a request is made
+//      success: Before the primary success handler is called
+//
+// To bind to an event, JQuery event attachers can be used on the object, e.g.
+//      $(observableHttpClient).bind("request", function (request) { ... });
+//
+// To begin a new recording session, use:
+//      var session = observableHttpClient.newSession();
+//
+// Requests and responses are then recorded in session.requests and session.responses. Session can be ended by session.end().
+// Multiple simultaneous sessions are supported.
+
+(function (window, undefined) {
+
+    var ObservableHttpClient = function (provider) {
+        this.provider = provider ? provider : OData.defaultHttpClient;
+    };
+
+    ObservableHttpClient.prototype.newSession = function () {
+        return new Session(this);
+    };
+
+    ObservableHttpClient.prototype.request = function (request, success, error) {
+        var that = this;
+
+        $(this).triggerHandler("request", request);
+        return this.provider.request(request, function (response) {
+            $(that).triggerHandler("success", response);
+            success(response);
+        }, error);
+    };
+
+
+    var Session = function (client) {
+        var that = this;
+
+        this.client = client;
+        this.clear();
+
+        this.requestHandler = function (event, request) { that.requests.push(request); };
+        $(client).bind("request", this.requestHandler);
+
+        this.successHandler = function (event, response) { that.responses.push(response); };
+        $(client).bind("success", this.successHandler);
+    };
+
+    Session.prototype.clear = function () {
+        this.requests = [];
+        this.responses = [];
+    }
+
+    Session.prototype.end = function () {
+        $(this.client).unbind("request", this.requestHandler);
+        $(this.client).unbind("success", this.successHandler);
+    };
+
+    window.ObservableHttpClient = ObservableHttpClient;
+
+})(this);
\ No newline at end of file
diff --git a/JSLib/tests/endpoints/FoodStoreDataService.svc b/JSLib/tests/endpoints/FoodStoreDataService.svc
new file mode 100644
index 0000000..9e7e332
--- /dev/null
+++ b/JSLib/tests/endpoints/FoodStoreDataService.svc
@@ -0,0 +1,430 @@
+<%@ ServiceHost Language="C#" Factory="System.Data.Services.DataServiceHostFactory, Microsoft.Data.Services, Version=5.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
+    Service="DataJS.Tests.V1.FoodStoreDataService" %>
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+namespace DataJS.Tests.V1
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Data.Services;
+    using System.Data.Services.Common;
+    using System.Linq;
+    using System.ServiceModel.Web;
+    using System.Web;
+
+    [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    public class FoodStoreDataService : DataService<FoodContainer>
+    {
+        // This method is called only once to initialize service-wide policies.
+        public static void InitializeService(DataServiceConfiguration config)
+        {
+            config.SetEntitySetAccessRule("*", EntitySetRights.All);
+            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
+            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V1;
+            config.UseVerboseErrors = true;
+        }
+        
+        [WebInvoke]
+        public void ResetData()
+        {
+            this.CurrentDataSource.ResetData();
+        }
+
+        [WebGet]
+        public IQueryable<string> FoodsAvailable()
+        {
+            return this.CurrentDataSource.Foods.Select(food => food.Name);
+        }
+
+        [WebGet]
+        public IQueryable<Package> PackagingTypes()
+        {
+            return this.CurrentDataSource.Foods.Select(food => food.Packaging);
+        }
+
+        [WebGet]
+        public string UserNameAndPassword()
+        {
+            var request = WebOperationContext.Current.IncomingRequest;
+            string authorization = request.Headers["Authorization"];
+            if (String.IsNullOrEmpty(authorization))
+            {
+                WebOperationContext.Current.OutgoingResponse.Headers["WWW-Authenticate"] = "Basic realm=\"localhost\"";
+                throw new DataServiceException(401, "Access denied in UserNameAndPassword");
+            }
+
+            return authorization;
+        }
+    }
+    
+    public class FoodContainer : ReflectionDataContext, IUpdatable
+    {
+        private static bool dataInitialized;
+
+        public IQueryable<Category> Categories
+        {
+            get { return this.GetResourceSetEntities<Category>("Categories").AsQueryable(); }
+        }
+        
+        public IQueryable<Food> Foods
+        {
+            get { return this.GetResourceSetEntities<Food>("Foods").AsQueryable(); }
+        }
+        
+        public IQueryable<SpecialDay> SpecialDays
+        {
+            get { return this.GetResourceSetEntities<SpecialDay>("SpecialDays").AsQueryable(); }
+        }
+
+        public void ResetData()
+        {
+            this.ClearData();
+            
+            int i = 0;
+            Category[] categories = new Category[]
+            {
+                new Category { CategoryID = i++, Name = "Baking Supplies", Foods = new List<Food>() },
+                new Category { CategoryID = i++, Name = "Condiments", Foods = new List<Food>() },
+                new Category { CategoryID = i++, Name = "Empty Category", Foods = new List<Food>() }
+            };
+            Array.ForEach(categories, (category) => this.GetResourceSetEntities<Category>("Categories").Add(category));
+            
+            i = 0;
+            Food[] foods = new Food[]
+            {            
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "flour",
+                    UnitPrice = .19999,
+                    ServingSize = 1,
+                    MeasurementUnit = "Cup",
+                    ProteinGrams = 3,
+                    FatGrams = 1,
+                    CarbohydrateGrams = 20,
+                    CaloriesPerServing = 140,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2010, 12, 25, 12, 0, 0),
+                    ItemGUID = new Guid("27272727272727272727272727272727"),
+                    Weight = 10f,
+                    AvailableUnits = 1,
+                    
+                    Packaging = new Package(){
+                        Type = null, 
+                        Color = String.Empty, 
+                        NumberPerPackage = int.MaxValue, 
+                        RequiresRefridgeration = false, 
+                        PackageDimensions = new Dimensions()
+                        {
+                            Length = Decimal.MaxValue, 
+                            Height = Int16.MaxValue, 
+                            Width = Int64.MaxValue, 
+                            Volume = double.MaxValue,   
+                        },
+                        ShipDate = new DateTime(2000, 12, 29)
+                    },
+                    
+                    Category = categories[0],
+                },
+                
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "sugar",
+                    UnitPrice = .2,
+                    ServingSize = 1,
+                    MeasurementUnit = "tsp",
+                    ProteinGrams = 0,
+                    FatGrams = 0,
+                    CarbohydrateGrams = 4,
+                    CaloriesPerServing = 16,
+                    IsAvailable = false,
+                    ExpirationDate = new DateTime(2011, 12, 28),
+                    ItemGUID = new Guid("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
+                    Weight = 0.1f,
+                    AvailableUnits = 0,
+
+                    Packaging = new Package(){
+                        Type = " ",
+                        Color = "BLUE",
+                        NumberPerPackage = int.MinValue,
+                        RequiresRefridgeration = true,
+                        PackageDimensions = new Dimensions(){
+                            Length = Decimal.MinValue,
+                            Height = Int16.MinValue,
+                            Width = Int64.MinValue,
+                            Volume = double.MinValue,
+                        },
+                        ShipDate = new DateTime(2000, 12, 29),
+                    },
+                    
+                    Category = categories[1],
+                },
+
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "1 Chicken Egg",
+                    UnitPrice = 0.55,
+                    MeasurementUnit = null,
+                    ServingSize = 1,
+                    ProteinGrams = 6,
+                    FatGrams = 1,
+                    CarbohydrateGrams = 1,
+                    CaloriesPerServing = 70,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2000, 12, 29),
+                    ItemGUID = new Guid("00000000000000000000000000000000"),
+                    Weight = 0,
+                    AvailableUnits = -128,
+                    
+                    Packaging = new Package(){
+                        Type = "18     - Carton",
+                        Color = " brown ",
+                        NumberPerPackage = 0,
+                        RequiresRefridgeration = true,
+                        PackageDimensions = null,
+                        ShipDate = new DateTime(2000, 12, 29),
+                    },
+                    
+                    Category = null,
+                },
+
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "Brown Sugar",
+                    UnitPrice = 1.6,
+                    ServingSize = 1,
+                    MeasurementUnit = "TSP.",
+                    ProteinGrams = 0,
+                    FatGrams = 0,
+                    CarbohydrateGrams = 5, 
+                    CaloriesPerServing = 16,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2011, 12, 28),
+                    ItemGUID = new Guid("0123456789abcdef0123456789abcdef"),
+                    Weight = 4.5f,
+                    AvailableUnits = 127,
+                    Packaging = null,
+                    Category = categories[1],
+                },
+                
+                new PreparedFood()
+                {
+                    FoodID = i++,
+                    Name = "Cobb Salad",
+                    UnitPrice = 1.99,
+                    ServingSize = -1,
+                    MeasurementUnit = "cups",
+                    ProteinGrams = 6,
+                    FatGrams = 1,
+                    CarbohydrateGrams = 3, 
+                    CaloriesPerServing = 5,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2000, 12, 29),
+                    ItemGUID = new Guid("0123456789abcdef0123456789abcdef"),
+                    Weight = 5.674f,
+                    AvailableUnits = 127,
+                    Packaging = null,
+                    Category = categories[1],
+                    Instructions = "1.) Open 2.) Eat",
+                    NumberOfIngredients = 4,
+                },
+                
+                new PreparedFood()
+                {
+                    FoodID = i++,
+                    Name = "Lasagna",
+                    UnitPrice = 0,
+                    ServingSize = 8,
+                    MeasurementUnit = " servings",
+                    ProteinGrams = 100,
+                    FatGrams = 4,
+                    CarbohydrateGrams = 27, 
+                    CaloriesPerServing = 389,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(1904, 2, 29),
+                    ItemGUID = new Guid("0123456789abcdef0123456789abcdef"),
+                    Weight = 0,
+                    AvailableUnits = 4,
+                    Packaging = new Package(){
+                        Type = "box",
+                        Color = " 1 ",
+                        NumberPerPackage = 1,
+                        RequiresRefridgeration = true,
+                        PackageDimensions = new Dimensions(){
+                            Length = 3,
+                            Height = 1,
+                            Width = 5,
+                            Volume = 1.5,
+                        },
+                        ShipDate = new DateTime(2000, 12, 29),
+                    },
+                    Category = categories[0],
+                    Instructions = "Bake in oven",
+                    NumberOfIngredients = 15,
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Chocolate"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Pizza"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Avocados"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Quinoa"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Oatmeal"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Peanut Butter"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Banana"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Yam"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Clam"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Spam"
+                }
+            };
+            Array.ForEach(foods, (food) => this.GetResourceSetEntities<Food>("Foods").Add(food));
+
+            categories[0].Foods.Add(foods[0]);
+            categories[1].Foods.Add(foods[2]);
+            categories[1].Foods.Add(foods[3]);
+
+            SpecialDay[] specialDays = new SpecialDay[]
+            {
+                new SpecialDay { ID = 0, Name = "Some date", Date = new DateTime(2010, 12, 29, 1, 2, 3, 456) },
+                
+                // These entries deliberately inject "date-like" XML/JSON strings into string properties
+                new SpecialDay { ID = 1, Name = "2010-12-29T01:02:03.456", Date = new DateTime(2010, 12, 29, 1, 2, 3, 456) },
+                new SpecialDay { ID = 2, Name = "/Date(1293584523456)/", Date = new DateTime(2010, 12, 29, 1, 2, 3, 456) }
+            };
+            Array.ForEach(specialDays, (specialDay) => this.GetResourceSetEntities<SpecialDay>("SpecialDays").Add(specialDay));
+        }
+
+        protected override void EnsureDataIsInitialized()
+        {
+            if (!dataInitialized)
+            {
+                this.ResetData();
+                dataInitialized = true;
+            }
+        }
+    }
+
+    public class Category
+    {
+        public int CategoryID { get; set; }
+        public string Name { get; set; }
+        public List<Food> Foods { get; set; }
+    }
+    
+    public class Food
+    {
+        // Primitive types
+        public int FoodID { get; set; }
+        public string Name { get; set; }
+        public double UnitPrice { get; set; }
+        public Decimal ServingSize { get; set; }
+        public string MeasurementUnit { get; set; }
+        public Byte ProteinGrams { get; set; }
+        public Int16 FatGrams { get; set; }
+        public Int32 CarbohydrateGrams { get; set; }
+        public Int64 CaloriesPerServing { get; set; }
+        public Boolean IsAvailable { get; set; }
+        public DateTime ExpirationDate { get; set; }
+        public Guid ItemGUID { get; set; }
+        public Single Weight { get; set; }
+        public sbyte AvailableUnits { get; set; }
+        
+        // Complex types
+        public Package Packaging { get; set; }
+        
+        // Navigation properties
+        public Category Category { get; set; }
+    }
+
+    public class Package
+    {
+        public string Type { get; set; }
+        public string Color { get; set; }
+        public int NumberPerPackage { get; set; }
+        public Boolean RequiresRefridgeration { get; set; }
+        public DateTime ShipDate { get; set; }
+        public Dimensions PackageDimensions { get; set; }
+    }
+
+    public class Dimensions
+    {
+        public Decimal Length { get; set; }
+        public Int16 Height { get; set; }
+        public Int64 Width { get; set; }
+        public double Volume { get; set; }
+    }
+
+    public class PreparedFood : Food
+    {
+        public string Instructions { get; set; }
+        public float NumberOfIngredients { get; set; }
+    }
+    
+    public class SpecialDay
+    {
+        public int ID { get; set; }
+        public string Name { get; set; }
+        public DateTime Date { get; set; }
+    }
+}
diff --git a/JSLib/tests/endpoints/FoodStoreDataServiceV2.svc b/JSLib/tests/endpoints/FoodStoreDataServiceV2.svc
new file mode 100644
index 0000000..40ddaa7
--- /dev/null
+++ b/JSLib/tests/endpoints/FoodStoreDataServiceV2.svc
@@ -0,0 +1,442 @@
+<%@ ServiceHost Language="C#" Factory="System.Data.Services.DataServiceHostFactory, Microsoft.Data.Services, Version=5.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
+    Service="DataJS.Tests.V2.FoodStoreDataService" %>
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+namespace DataJS.Tests.V2
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Data.Services;
+    using System.Data.Services.Common;
+    using System.Linq;
+    using System.ServiceModel.Web;
+    using System.Web;
+    
+    /// <summary>
+    /// Provides a service similar to FoodStoreDataService, but uses V2 
+    /// features.
+    /// </summary>
+    [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    public class FoodStoreDataService : DataService<FoodContainer>
+    {
+        // This method is called only once to initialize service-wide policies.
+        public static void InitializeService(DataServiceConfiguration config)
+        {
+            config.SetEntitySetAccessRule("*", EntitySetRights.All);
+            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
+            config.UseVerboseErrors = true;
+            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
+            // Set Foods page size to 5 for cache testing
+            config.SetEntitySetPageSize("Foods", 5);
+            
+            // Make the Categories set paged to have a paged feed
+            config.SetEntitySetPageSize("Categories", 1);
+        }
+        
+        [WebInvoke]
+        public string ResetData()
+        {
+            this.CurrentDataSource.ResetData();
+            return "Data Reset";
+        }
+
+        [WebGet]
+        public IQueryable<string> FoodsAvailable()
+        {
+            return this.CurrentDataSource.Foods.Select(food => food.Name);
+        }
+
+        [WebGet]
+        public IQueryable<Package> PackagingTypes()
+        {
+            return this.CurrentDataSource.Foods.Select(food => food.Packaging);
+        }
+    }
+
+    public class FoodContainer : ReflectionDataContext, IUpdatable
+    {
+        private static bool dataInitialized;
+
+        public IQueryable<Category> Categories
+        {
+            get { return this.GetResourceSetEntities<Category>("Categories").AsQueryable(); }
+        }
+        
+        public IQueryable<Food> Foods
+        {
+            get { return this.GetResourceSetEntities<Food>("Foods").AsQueryable(); }
+        }
+
+        public void ResetData()
+        {
+            this.ClearData();
+            
+            int i = 0;
+            Category[] categories = new Category[]
+            {
+                new Category { CategoryID = i++, Name = "Baking Supplies" },
+                new Category { CategoryID = i++, Name = "Condiments" },
+                new Category { CategoryID = i++, Name = "Empty Category" }
+            };
+            Array.ForEach(categories, (category) => this.GetResourceSetEntities<Category>("Categories").Add(category));
+            
+            i = 0;
+            Food[] foods = new Food[]
+            {            
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "flour",
+                    UnitPrice = .19999,
+                    ServingSize = 1,
+                    MeasurementUnit = "Cup",
+                    ProteinGrams = 3,
+                    FatGrams = 1,
+                    CarbohydrateGrams = 20,
+                    CaloriesPerServing = 140,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2010, 12, 25, 12, 0, 0),
+                    ItemGUID = new Guid("27272727272727272727272727272727"),
+                    Weight = 10f,
+                    AvailableUnits = 1,
+                    
+                    Packaging = new Package(){
+                        Type = null, 
+                        Color = String.Empty, 
+                        NumberPerPackage = int.MaxValue, 
+                        RequiresRefridgeration = false, 
+                        PackageDimensions = new Dimensions()
+                        {
+                            Length = Decimal.MaxValue, 
+                            Height = Int16.MaxValue, 
+                            Width = Int64.MaxValue, 
+                            Volume = double.MaxValue,   
+                        },
+                        ShipDate = new DateTime(2000, 12, 29)
+                    },
+                    
+                    CookedSize = new CookedDimensions()
+                    {
+                        Height = 1,
+                        Length = 2,
+                        Width = 3,
+                        Volume = 1 * 2 * 3
+                    },
+                    
+                    Category = categories[0],
+                },
+                
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "sugar",
+                    UnitPrice = .2,
+                    ServingSize = 1,
+                    MeasurementUnit = "tsp",
+                    ProteinGrams = 0,
+                    FatGrams = 0,
+                    CarbohydrateGrams = 4,
+                    CaloriesPerServing = 16,
+                    IsAvailable = false,
+                    ExpirationDate = new DateTime(2011, 12, 28),
+                    ItemGUID = new Guid("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
+                    Weight = 0.1f,
+                    AvailableUnits = 0,
+
+                    Packaging = new Package(){
+                        Type = " ",
+                        Color = "BLUE",
+                        NumberPerPackage = int.MinValue,
+                        RequiresRefridgeration = true,
+                        PackageDimensions = new Dimensions(){
+                            Length = Decimal.MinValue,
+                            Height = Int16.MinValue,
+                            Width = Int64.MinValue,
+                            Volume = double.MinValue,
+                        },
+                        ShipDate = new DateTime(2000, 12, 29),
+                    },
+                    
+                    Category = categories[1],
+                },
+
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "1 Chicken Egg",
+                    UnitPrice = 0.55,
+                    MeasurementUnit = null,
+                    ServingSize = 1,
+                    ProteinGrams = 6,
+                    FatGrams = 1,
+                    CarbohydrateGrams = 1,
+                    CaloriesPerServing = 70,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2000, 12, 29),
+                    ItemGUID = new Guid("00000000000000000000000000000000"),
+                    Weight = 0,
+                    AvailableUnits = -128,
+                    
+                    Packaging = new Package(){
+                        Type = "18     - Carton",
+                        Color = " brown ",
+                        NumberPerPackage = 0,
+                        RequiresRefridgeration = true,
+                        PackageDimensions = null,
+                        ShipDate = new DateTime(2000, 12, 29),
+                    },
+                    
+                    Category = null,
+                },
+
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "Brown Sugar",
+                    UnitPrice = 1.6,
+                    ServingSize = 1,
+                    MeasurementUnit = "TSP.",
+                    ProteinGrams = 0,
+                    FatGrams = 0,
+                    CarbohydrateGrams = 5, 
+                    CaloriesPerServing = 16,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2011, 12, 28),
+                    ItemGUID = new Guid("0123456789abcdef0123456789abcdef"),
+                    Weight = 4.5f,
+                    AvailableUnits = 127,
+                    Packaging = null,
+                    Category = categories[1],
+                },
+                
+                new PreparedFood()
+                {
+                    FoodID = i++,
+                    Name = "Cobb Salad",
+                    UnitPrice = 1.99,
+                    ServingSize = -1,
+                    MeasurementUnit = "cups",
+                    ProteinGrams = 6,
+                    FatGrams = 1,
+                    CarbohydrateGrams = 3, 
+                    CaloriesPerServing = 5,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2000, 12, 29),
+                    ItemGUID = new Guid("0123456789abcdef0123456789abcdef"),
+                    Weight = 5.674f,
+                    AvailableUnits = 127,
+                    Packaging = null,
+                    Category = categories[1],
+                    Instructions = "1.) Open 2.) Eat",
+                    NumberOfIngredients = 4,
+                },
+                
+                new PreparedFood()
+                {
+                    FoodID = i++,
+                    Name = "Lasagna",
+                    UnitPrice = 0,
+                    ServingSize = 8,
+                    MeasurementUnit = " servings",
+                    ProteinGrams = 100,
+                    FatGrams = 4,
+                    CarbohydrateGrams = 27, 
+                    CaloriesPerServing = 389,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(1904, 2, 29),
+                    ItemGUID = new Guid("0123456789abcdef0123456789abcdef"),
+                    Weight = 0,
+                    AvailableUnits = 4,
+                    Packaging = new Package(){
+                        Type = "box",
+                        Color = " 1 ",
+                        NumberPerPackage = 1,
+                        RequiresRefridgeration = true,
+                        PackageDimensions = new Dimensions(){
+                            Length = 3,
+                            Height = 1,
+                            Width = 5,
+                            Volume = 1.5,
+                        },
+                        ShipDate = new DateTime(2000, 12, 29),
+                    },
+                    Category = categories[0],
+                    Instructions = "Bake in oven",
+                    NumberOfIngredients = 15,
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Chocolate"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Pizza"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Avocados"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Quinoa"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Oatmeal"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Peanut Butter"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Banana"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Yam"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Clam"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Spam"
+                }
+            };
+            Array.ForEach(foods, (food) => this.GetResourceSetEntities<Food>("Foods").Add(food));
+
+            categories[0].Foods.Add(foods[0]);
+            categories[1].Foods.Add(foods[2]);
+            categories[1].Foods.Add(foods[3]);
+        }
+
+        protected override void EnsureDataIsInitialized()
+        {
+            if (!dataInitialized)
+            {
+                this.ResetData();
+                dataInitialized = true;
+            }
+        }
+    }
+
+    /// <summary>
+    /// The Category class is a simple class with V1-compatible feed customizations.
+    /// </summary>
+    [EntityPropertyMapping("Name", SyndicationItemProperty.Title, SyndicationTextContentKind.Plaintext, true)]
+    [DataServiceKey("CategoryID")]
+    public class Category
+    {
+        public Category()
+        {
+            this.Foods = new List<Food>();
+        }
+        
+        public int CategoryID { get; set; }
+        public string Name { get; set; }
+        public List<Food> Foods { get; set; }
+    }
+    
+    /// <summary>
+    /// The Food class has a mixture of V1-compatible and incompatible feed
+    /// customizations (thus it's V2), and custom mappings.
+    /// </summary>
+    [EntityPropertyMapping("Name", SyndicationItemProperty.Title, SyndicationTextContentKind.Plaintext, true)]
+    [EntityPropertyMapping("UnitPrice", "price/@value", "pr", "http://www.example.org/price/", false)]
+    [EntityPropertyMapping("CookedSize/Length", "cooked/@length", "cooked", "http://www.example.org/cooked/", false)]
+    [EntityPropertyMapping("CookedSize/Height", "cooked/@height", "cooked", "http://www.example.org/cooked/", false)]
+    [EntityPropertyMapping("CookedSize/Width", "cooked/@width", "cooked", "http://www.example.org/cooked/", false)]
+    [EntityPropertyMapping("CookedSize/Volume", "cooked/volume", "cooked", "http://www.example.org/cooked/", false)]
+    [DataServiceKey("FoodID")]
+    public class Food
+    {
+        // Primitive types
+        public int FoodID { get; set; }
+        public string Name { get; set; }
+        public double UnitPrice { get; set; }
+        public Decimal ServingSize { get; set; }
+        public string MeasurementUnit { get; set; }
+        public Byte ProteinGrams { get; set; }
+        public Int16 FatGrams { get; set; }
+        public Int32 CarbohydrateGrams { get; set; }
+        public Int64 CaloriesPerServing { get; set; }
+        public Boolean IsAvailable { get; set; }
+        public DateTime ExpirationDate { get; set; }
+        public Guid ItemGUID { get; set; }
+        public Single Weight { get; set; }
+        public sbyte AvailableUnits { get; set; }
+        
+        // Complex types
+        public Package Packaging { get; set; }
+        public CookedDimensions CookedSize { get; set; }
+        
+        // Navigation properties
+        public Category Category { get; set; }
+    }
+
+    public class Package
+    {
+        public string Type { get; set; }
+        public string Color { get; set; }
+        public int NumberPerPackage { get; set; }
+        public Boolean RequiresRefridgeration { get; set; }
+        public DateTime ShipDate { get; set; }
+        public Dimensions PackageDimensions { get; set; }
+    }
+
+    public class Dimensions
+    {
+        public Decimal Length { get; set; }
+        public Int16 Height { get; set; }
+        public Int64 Width { get; set; }
+        public double Volume { get; set; }
+    }
+
+    public class CookedDimensions
+    {
+        public Decimal Length { get; set; }
+        public Int16 Height { get; set; }
+        public Int64 Width { get; set; }
+        public double Volume { get; set; }
+    }
+
+    public class PreparedFood : Food
+    {
+        public string Instructions { get; set; }
+        public float NumberOfIngredients { get; set; }
+    }
+}
diff --git a/JSLib/tests/endpoints/FoodStoreDataServiceV3.svc b/JSLib/tests/endpoints/FoodStoreDataServiceV3.svc
new file mode 100644
index 0000000..6ad9671
--- /dev/null
+++ b/JSLib/tests/endpoints/FoodStoreDataServiceV3.svc
@@ -0,0 +1,583 @@
+<%@ ServiceHost Language="C#" Factory="System.Data.Services.DataServiceHostFactory, Microsoft.Data.Services, Version=5.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
+    Service="DataJS.Tests.V3.FoodStoreDataService" %>
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+namespace DataJS.Tests.V3
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Data.Services;
+    using System.Data.Services.Common;
+    using System.Data.Services.Providers;
+    using System.Linq;
+    using System.ServiceModel.Web;
+    using System.Web;
+    using System.IO;
+    using System.Spatial;
+    
+    /// <summary>
+    /// Provides a service similar to FoodStoreDataService, but uses V3 and WCF Data Services 5.x
+    /// features.
+    /// </summary>
+    [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    public class FoodStoreDataService : DataService<FoodContainer>
+    {
+        // This method is called only once to initialize service-wide policies.
+        public static void InitializeService(DataServiceConfiguration config)
+        {
+            config.SetEntitySetAccessRule("*", EntitySetRights.All);
+            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
+            config.UseVerboseErrors = true;
+            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
+            // Set Foods page size to 5 for cache testing
+            config.SetEntitySetPageSize("Foods", 5);
+            // Make the Categories set paged to have a paged feed
+            config.SetEntitySetPageSize("Categories", 1);
+        }
+        
+        [WebInvoke]
+        public string ResetData()
+        {
+            this.CurrentDataSource.ResetData();
+            return "Data Reset";
+        }
+
+        [WebGet]
+        public IQueryable<string> FoodsAvailable()
+        {
+            return this.CurrentDataSource.Foods.Select(food => food.Name);
+        }
+
+        [WebGet]
+        public IQueryable<Package> PackagingTypes()
+        {
+            return this.CurrentDataSource.Foods.Select(food => food.Packaging);
+        }
+    }
+
+    public class FoodContainer : ReflectionDataContext, IUpdatable, IDataServiceStreamProvider2
+    {
+        private static bool dataInitialized;
+
+        public IQueryable<Category> Categories
+        {
+            get { return this.GetResourceSetEntities<Category>("Categories").AsQueryable(); }
+        }
+        
+        public IQueryable<Food> Foods
+        {
+            get { return this.GetResourceSetEntities<Food>("Foods").AsQueryable(); }
+        }
+
+        public void ResetData()
+        {
+            this.ClearData();
+
+            var builder = SpatialImplementation.CurrentImplementation.CreateBuilder();
+            builder.GeometryPipeline.SetCoordinateSystem(CoordinateSystem.DefaultGeography);
+            builder.GeometryPipeline.BeginGeometry(SpatialType.Collection);
+            builder.GeometryPipeline.BeginFigure(new GeometryPosition(5.0, 5.0));
+            builder.GeometryPipeline.EndFigure();
+            builder.GeometryPipeline.EndGeometry();
+            
+            int i = 0;
+            Category[] categories = new Category[]
+            {
+                new Category { CategoryID = i++, Name = "Baking Supplies" },
+                new Category { CategoryID = i++, Name = "Condiments" },
+                new Category { CategoryID = i++, Name = "Empty Category" }
+            };
+            Array.ForEach(categories, (category) => this.GetResourceSetEntities<Category>("Categories").Add(category));
+            
+            i = 0;
+            Food[] foods = new Food[]
+            {            
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "flour",
+                    UnitPrice = .19999,
+                    ServingSize = 1,
+                    MeasurementUnit = "Cup",
+                    ProteinGrams = 3,
+                    FatGrams = 1,
+                    CarbohydrateGrams = 20,
+                    CaloriesPerServing = 140,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2010, 12, 25, 12, 0, 0),
+                    ItemGUID = new Guid("27272727272727272727272727272727"),
+                    Weight = 10f,
+                    AvailableUnits = 1,
+                    
+                    Packaging = new Package(){
+                        Type = null, 
+                        Color = String.Empty, 
+                        NumberPerPackage = int.MaxValue, 
+                        RequiresRefridgeration = false, 
+                        PackageDimensions = new Dimensions()
+                        {
+                            Length = Decimal.MaxValue, 
+                            Height = Int16.MaxValue, 
+                            Width = Int64.MaxValue, 
+                            Volume = double.MaxValue,   
+                        },
+                        ShipDate = new DateTime(2000, 12, 29)
+                    },
+                    
+                    CookedSize = new CookedDimensions()
+                    {
+                        Height = 1,
+                        Length = 2,
+                        Width = 3,
+                        Volume = 1 * 2 * 3
+                    },
+                    
+                    Category = categories[0],
+                    
+                    AlternativeNames = new List<string>() {"ground cereal", "ground grain"},
+                    
+                    Providers = new List<Provider> {
+                        new Provider() { 
+                             Name= "Flour Provider", 
+                             Aliases = new List<string>() {"fp1", "flour provider1"},
+                             Details = new ProviderDetails() {
+                                 Telephone= "555-555-555",
+                                 PreferredCode = 1001
+                             }
+                        },
+                        new Provider() { 
+                             Name= "Ground Grains", 
+                             Aliases = new List<string>()
+                        }
+                    },
+                    
+                    SpatialData = (GeometryCollection)builder.ConstructedGeometry 
+                },
+                
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "sugar",
+                    UnitPrice = .2,
+                    ServingSize = 1,
+                    MeasurementUnit = "tsp",
+                    ProteinGrams = 0,
+                    FatGrams = 0,
+                    CarbohydrateGrams = 4,
+                    CaloriesPerServing = 16,
+                    IsAvailable = false,
+                    ExpirationDate = new DateTime(2011, 12, 28),
+                    ItemGUID = new Guid("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
+                    Weight = 0.1f,
+                    AvailableUnits = 0,
+
+                    Packaging = new Package(){
+                        Type = " ",
+                        Color = "BLUE",
+                        NumberPerPackage = int.MinValue,
+                        RequiresRefridgeration = true,
+                        PackageDimensions = new Dimensions(){
+                            Length = Decimal.MinValue,
+                            Height = Int16.MinValue,
+                            Width = Int64.MinValue,
+                            Volume = double.MinValue,
+                        },
+                        ShipDate = new DateTime(2000, 12, 29),
+                    },
+                    
+                    Category = categories[1],
+                },
+
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "1 Chicken Egg",
+                    UnitPrice = 0.55,
+                    MeasurementUnit = null,
+                    ServingSize = 1,
+                    ProteinGrams = 6,
+                    FatGrams = 1,
+                    CarbohydrateGrams = 1,
+                    CaloriesPerServing = 70,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2000, 12, 29),
+                    ItemGUID = new Guid("00000000000000000000000000000000"),
+                    Weight = 0,
+                    AvailableUnits = -128,
+                    
+                    Packaging = new Package(){
+                        Type = "18     - Carton",
+                        Color = " brown ",
+                        NumberPerPackage = 0,
+                        RequiresRefridgeration = true,
+                        PackageDimensions = null,
+                        ShipDate = new DateTime(2000, 12, 29),
+                    },
+                    
+                    Category = null,
+                },
+
+                new Food()
+                {
+                    FoodID = i++,
+                    Name = "Brown Sugar",
+                    UnitPrice = 1.6,
+                    ServingSize = 1,
+                    MeasurementUnit = "TSP.",
+                    ProteinGrams = 0,
+                    FatGrams = 0,
+                    CarbohydrateGrams = 5, 
+                    CaloriesPerServing = 16,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2011, 12, 28),
+                    ItemGUID = new Guid("0123456789abcdef0123456789abcdef"),
+                    Weight = 4.5f,
+                    AvailableUnits = 127,
+                    Packaging = null,
+                    Category = categories[1],
+                },
+                
+                new PreparedFood()
+                {
+                    FoodID = i++,
+                    Name = "Cobb Salad",
+                    UnitPrice = 1.99,
+                    ServingSize = -1,
+                    MeasurementUnit = "cups",
+                    ProteinGrams = 6,
+                    FatGrams = 1,
+                    CarbohydrateGrams = 3, 
+                    CaloriesPerServing = 5,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(2000, 12, 29),
+                    ItemGUID = new Guid("0123456789abcdef0123456789abcdef"),
+                    Weight = 5.674f,
+                    AvailableUnits = 127,
+                    Packaging = null,
+                    Category = categories[1],
+                    Instructions = "1.) Open 2.) Eat",
+                    NumberOfIngredients = 4,
+                },
+                
+                new PreparedFood()
+                {
+                    FoodID = i++,
+                    Name = "Lasagna",
+                    UnitPrice = 0,
+                    ServingSize = 8,
+                    MeasurementUnit = " servings",
+                    ProteinGrams = 100,
+                    FatGrams = 4,
+                    CarbohydrateGrams = 27, 
+                    CaloriesPerServing = 389,
+                    IsAvailable = true,
+                    ExpirationDate = new DateTime(1904, 2, 29),
+                    ItemGUID = new Guid("0123456789abcdef0123456789abcdef"),
+                    Weight = 0,
+                    AvailableUnits = 4,
+                    Packaging = new Package(){
+                        Type = "box",
+                        Color = " 1 ",
+                        NumberPerPackage = 1,
+                        RequiresRefridgeration = true,
+                        PackageDimensions = new Dimensions(){
+                            Length = 3,
+                            Height = 1,
+                            Width = 5,
+                            Volume = 1.5,
+                        },
+                        ShipDate = new DateTime(2000, 12, 29),
+                    },
+                    Category = categories[0],
+                    Instructions = "Bake in oven",
+                    NumberOfIngredients = 15,
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Chocolate"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Pizza"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Avocados"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Quinoa"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Oatmeal"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Peanut Butter"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Banana"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Yam"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Clam"
+                },
+                
+                new Food()
+                {                    
+                    FoodID = i++,
+                    Name = "Spam"
+                }
+            };
+            Array.ForEach(foods, (food) => this.GetResourceSetEntities<Food>("Foods").Add(food));
+
+            categories[0].Foods.Add(foods[0]);
+            categories[1].Foods.Add(foods[2]);
+            categories[1].Foods.Add(foods[3]);
+        }
+
+        protected override void EnsureDataIsInitialized()
+        {
+            if (!dataInitialized)
+            {
+                this.ResetData();
+                dataInitialized = true;
+            }
+        }
+
+        public Stream GetReadStream(object entity, ResourceProperty streamProperty, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext)
+        {
+            return new MemoryStream();
+        }
+
+        public Uri GetReadStreamUri(object entity, ResourceProperty streamProperty, DataServiceOperationContext operationContext)
+        {
+            if (streamProperty.Name == "Icon")
+            {
+                return null;
+            }
+            return new Uri(operationContext.AbsoluteServiceUri, streamProperty.Name);
+        }
+
+        public string GetStreamContentType(object entity, ResourceProperty streamProperty, DataServiceOperationContext operationContext)
+        {
+            if (streamProperty.Name == "Icon")
+            {
+                return "image/gif";
+            }
+            return "image/png";
+        }
+
+        public string GetStreamETag(object entity, ResourceProperty streamProperty, DataServiceOperationContext operationContext)
+        {
+            return "W/\"123456789\"";
+        }
+
+        public Stream GetWriteStream(object entity, ResourceProperty streamProperty, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext)
+        {
+            return new MemoryStream();
+        }
+
+        public void DeleteStream(object entity, DataServiceOperationContext operationContext)
+        {
+            // do nothing.
+        }
+
+        public Stream GetReadStream(object entity, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Uri GetReadStreamUri(object entity, DataServiceOperationContext operationContext)
+        {
+            throw new NotImplementedException();
+        }
+
+        public string GetStreamContentType(object entity, DataServiceOperationContext operationContext)
+        {
+            throw new NotImplementedException();
+        }
+
+        public string GetStreamETag(object entity, DataServiceOperationContext operationContext)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Stream GetWriteStream(object entity, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext)
+        {
+            throw new NotImplementedException();
+        }
+
+        public string ResolveType(string entitySetName, DataServiceOperationContext operationContext)
+        {
+            throw new NotImplementedException();
+        }
+
+        public int StreamBufferSize
+        {
+            get { return 1024; }
+        }
+    }
+
+    /// <summary>
+    /// The Category class is a simple class with V1-compatible feed customizations.
+    /// </summary>
+    [EntityPropertyMapping("Name", SyndicationItemProperty.Title, SyndicationTextContentKind.Plaintext, true)]
+    [DataServiceKey("CategoryID")]
+    [EntitySet("Categories")]
+    [NamedStream("Icon")]
+    public class Category
+    {
+        public Category()
+        {
+            this.Foods = new List<Food>();
+        }
+        
+        public int CategoryID { get; set; }
+        public string Name { get; set; }
+        public List<Food> Foods { get; set; }
+    }
+    
+    /// <summary>
+    /// The Food class has a mixture of V1-compatible and incompatible feed
+    /// customizations (thus it's V2), and custom mappings.
+    /// </summary>
+    [EntityPropertyMapping("Name", SyndicationItemProperty.Title, SyndicationTextContentKind.Plaintext, true)]
+    [EntityPropertyMapping("UnitPrice", "price/@value", "pr", "http://www.example.org/price/", false)]
+    [EntityPropertyMapping("CookedSize/Length", "cooked/@length", "cooked", "http://www.example.org/cooked/", false)]
+    [EntityPropertyMapping("CookedSize/Height", "cooked/@height", "cooked", "http://www.example.org/cooked/", false)]
+    [EntityPropertyMapping("CookedSize/Width", "cooked/@width", "cooked", "http://www.example.org/cooked/", false)]
+    [EntityPropertyMapping("CookedSize/Volume", "cooked/volume", "cooked", "http://www.example.org/cooked/", false)]
+    [DataServiceKey("FoodID")]
+    [EntitySet("Foods")]
+    [NamedStream("Picture")]
+    public class Food
+    {
+        private List<string> alternativeNames = new List<string>();
+        private List<Provider> providers = new List<Provider>();
+        
+        // Primitive types
+        public int FoodID { get; set; }
+        public string Name { get; set; }
+        public double UnitPrice { get; set; }
+        public Decimal ServingSize { get; set; }
+        public string MeasurementUnit { get; set; }
+        public Byte ProteinGrams { get; set; }
+        public Int16 FatGrams { get; set; }
+        public Int32 CarbohydrateGrams { get; set; }
+        public Int64 CaloriesPerServing { get; set; }
+        public Boolean IsAvailable { get; set; }
+        public DateTime ExpirationDate { get; set; }
+        public Guid ItemGUID { get; set; }
+        public Single Weight { get; set; }
+        public sbyte AvailableUnits { get; set; }
+
+        // Complex types
+        public Package Packaging { get; set; }
+        public CookedDimensions CookedSize { get; set; }
+
+        // Navigation properties
+        public Category Category { get; set; }
+
+        // Collection properties
+        public List<string> AlternativeNames
+        {
+            get { return alternativeNames; }
+            set { alternativeNames = value; }
+        }
+
+        public List<Provider> Providers
+        {
+            get { return providers; }
+            set { providers = value; }
+        }
+
+        public GeometryCollection SpatialData
+        {
+            get;
+            set;
+        }
+        
+    }
+
+    public class Provider
+    {
+        public string Name { get; set; }
+        public List<string> Aliases { get; set; }
+        public ProviderDetails Details { get; set; }
+    }
+
+    public class ProviderDetails
+    {
+        public string Telephone { get; set; }
+        public int PreferredCode { get; set; }
+    }
+    
+    public class Package
+    {
+        public string Type { get; set; }
+        public string Color { get; set; }
+        public int NumberPerPackage { get; set; }
+        public Boolean RequiresRefridgeration { get; set; }
+        public DateTime ShipDate { get; set; }
+        public Dimensions PackageDimensions { get; set; }
+    }
+
+    public class Dimensions
+    {
+        public Decimal Length { get; set; }
+        public Int16 Height { get; set; }
+        public Int64 Width { get; set; }
+        public double Volume { get; set; }
+    }
+
+    public class CookedDimensions
+    {
+        public Decimal Length { get; set; }
+        public Int16 Height { get; set; }
+        public Int64 Width { get; set; }
+        public double Volume { get; set; }
+    }
+
+    public class PreparedFood : Food
+    {
+        public string Instructions { get; set; }
+        public float NumberOfIngredients { get; set; }
+    }
+}
diff --git a/JSLib/tests/endpoints/web.config b/JSLib/tests/endpoints/web.config
new file mode 100644
index 0000000..4875aa4
--- /dev/null
+++ b/JSLib/tests/endpoints/web.config
@@ -0,0 +1,27 @@
+<?xml version='1.0'?>
+<configuration>
+  <system.web>
+    <compilation debug='true'>
+      <assemblies>
+        <add assembly='System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089'/>
+        <add assembly='System.Data.DataSetExtensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089'/>
+        <add assembly='System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35'/>
+        <add assembly='System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089'/>
+        <add assembly='System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089'/>
+        <add assembly='System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089'/>
+        <add assembly='System.ServiceModel.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35'/>
+
+        <add assembly='Microsoft.Data.OData, Version=5.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35'/>
+        <add assembly='Microsoft.Data.Services, Version=5.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35'/>
+        <add assembly='Microsoft.Data.Services.Client, Version=5.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35'/>
+      </assemblies>
+    </compilation>
+  </system.web>
+  <system.codedom>
+    <compilers>
+      <compiler language='c#;cs;csharp' extension='.cs' type='Microsoft.CSharp.CSharpCodeProvider,System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'>
+        <providerOption name='CompilerVersion' value='v4.0' />
+      </compiler>
+    </compilers>
+  </system.codedom>
+</configuration>
diff --git a/JSLib/tests/odata-atom-tests.js b/JSLib/tests/odata-atom-tests.js
new file mode 100644
index 0000000..52c0c94
--- /dev/null
+++ b/JSLib/tests/odata-atom-tests.js
@@ -0,0 +1,4756 @@
+/// <reference path="../src/odata-utils.js" />
+/// <reference path="../src/odata-handler.js" />
+/// <reference path="../src/odata-atom.js" />
+/// <reference path="../src/odata-xml.js" />
+/// <reference path="common/djstest.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-atom-tests.js
+
+(function (window, undefined) {
+
+    module("Unit");
+
+    var parseMetadataHelper = function (text) {
+        var response = { statusCode: 200, body: text, headers: { "Content-Type": "application/xml"} };
+        OData.metadataHandler.read(response, {});
+        return response.data;
+    };
+
+    var resetFoodData = function () {
+        $.ajax({ url: "./endpoints/FoodStoreDataServiceV2.svc/ResetData", async: false, type: "POST" });
+    };
+
+    var customerSampleMetadataText = '' +
+    '<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">\r\n' +
+    '<edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="2.0">\r\n' +
+    '<Schema Namespace="Ns" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns="http://schemas.microsoft.com/ado/2006/04/edm">\r\n' +
+    '    <EntityType Name="Customer">\r\n' +
+    '    <Key><PropertyRef Name="ID" /></Key>\r\n' +
+    '     <Property Name="ID" Type="Edm.Int32" Nullable="false" />\r\n' +
+    '     <Property Name="Name" Type="Edm.String" Nullable="true" m:FC_TargetPath="SyndicationSummary" m:FC_ContentKind="xhtml" m:FC_KeepInContent="false" />\r\n' +
+    '     <Property Name="LastName" Type="Edm.String" Nullable="true" m:FC_TargetPath="foo/bar/@baz" m:FC_NsUri="htp://prefix" m:FC_NsPrefix="prefix" m:FC_KeepInContent="false" />\r\n' +
+    '     <Property Name="FavoriteNumber" Type="Edm.Int32" Nullable="true" m:FC_TargetPath="favorite/number" m:FC_NsUri="htp://prefix" m:FC_NsPrefix="prefix" m:FC_KeepInContent="false" />\r\n' +
+    '     <Property Name="Address" Type="Ns.Address" Nullable="false" \r\n' +
+    '       m:FC_TargetPath="foo/bar/@city" m:FC_NsUri="htp://prefix" m:FC_NsPrefix="prefix" m:FC_SourcePath="City" m:FC_KeepInContent="false" \r\n' +
+    '       m:FC_TargetPath_1="foo/bar" m:FC_NsUri_1="htp://prefix" m:FC_NsPrefix_1="prefix" m:FC_SourcePath_1="Street" m:FC_KeepInContent_1="false" />\r\n' +
+    '    </EntityType>\r\n' +
+    '    <ComplexType Name="Address">\r\n' +
+    '     <Property Name="Street" Type="Edm.String" Nullable="true" />\r\n' +
+    '     <Property Name="City" Type="Edm.String" Nullable="true" />\r\n' +
+    '    </ComplexType>\r\n' +
+    '    <EntityContainer Name="SampleContext" m:IsDefaultEntityContainer="true">\r\n' +
+    '     <EntitySet Name="Customers" EntityType="Ns.Customer" />\r\n' +
+    '    </EntityContainer>\r\n' +
+    '</Schema>\r\n' +
+    '</edmx:DataServices></edmx:Edmx>';
+
+    var foodServiceV2FoodsSampleText = '' +
+    '<feed xml:base="http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">' +
+    '  <title type="text">Foods</title>' +
+    '  <id>http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/Foods</id>' +
+    '  <updated>2010-12-28T23:09:54Z</updated>' +
+    '  <link rel="self" title="Foods" href="Foods" />' +
+    '  <entry>' +
+    '    <id>http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/Foods(0)</id>' +
+    '    <title type="text">flour</title>' +
+    '    <updated>2010-12-28T23:09:54Z</updated>' +
+    '    <author><name /></author>' +
+    '    <link rel="edit" title="Food" href="Foods(0)" />' +
+    '    <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(0)/Category" />' +
+    '    <category term="DataJS.Tests.V2.Food" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+    '    <content type="application/xml">' +
+    '      <m:properties>' +
+    '        <d:FoodID m:type="Edm.Int32">0</d:FoodID>' +
+    '        <d:Name>flour</d:Name>' +
+    '        <d:ServingSize m:type="Edm.Decimal">1</d:ServingSize>' +
+    '        <d:MeasurementUnit>Cup</d:MeasurementUnit>' +
+    '        <d:ProteinGrams m:type="Edm.Byte">3</d:ProteinGrams>' +
+    '        <d:FatGrams m:type="Edm.Int16">1</d:FatGrams>' +
+    '        <d:CarbohydrateGrams m:type="Edm.Int32">20</d:CarbohydrateGrams>' +
+    '        <d:CaloriesPerServing m:type="Edm.Int64">140</d:CaloriesPerServing>' +
+    '        <d:IsAvailable m:type="Edm.Boolean">true</d:IsAvailable>' +
+    '        <d:ExpirationDate m:type="Edm.DateTime">2010-12-25T12:00:00</d:ExpirationDate>' +
+    '        <d:ItemGUID m:type="Edm.Guid">27272727-2727-2727-2727-272727272727</d:ItemGUID>' +
+    '        <d:Weight m:type="Edm.Single">10</d:Weight>' +
+    '        <d:AvailableUnits m:type="Edm.SByte">1</d:AvailableUnits>' +
+    '        <d:Packaging m:type="DataJS.Tests.V2.Package">' +
+    '          <d:Type m:null="true" />' +
+    '          <d:Color></d:Color>' +
+    '          <d:NumberPerPackage m:type="Edm.Int32">2147483647</d:NumberPerPackage>' +
+    '          <d:RequiresRefridgeration m:type="Edm.Boolean">false</d:RequiresRefridgeration>' +
+    '          <d:ShipDate m:type="Edm.DateTime">0001-01-01T00:00:00</d:ShipDate>' +
+    '          <d:PackageDimensions m:type="DataJS.Tests.V2.Dimensions">' +
+    '            <d:Length m:type="Edm.Decimal">79228162514264337593543950335</d:Length>' +
+    '            <d:Height m:type="Edm.Int16">32767</d:Height>' +
+    '            <d:Width m:type="Edm.Int64">9223372036854775807</d:Width>' +
+    '            <d:Volume m:type="Edm.Double">1.7976931348623157E+308</d:Volume>' +
+    '          </d:PackageDimensions>' +
+    '        </d:Packaging>' +
+    '      </m:properties>' +
+    '    </content>' +
+    '    <cooked:cooked cooked:length="2" cooked:height="1" cooked:width="3" xmlns:cooked="http://www.example.org/cooked/">' +
+    '      <cooked:volume>6</cooked:volume>' +
+    '    </cooked:cooked>' +
+    '    <pr:price pr:value="0.19999" xmlns:pr="http://www.example.org/price/"></pr:price>' +
+    '  </entry>' +
+    '  <entry>' +
+    '    <id>http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/Foods(1)</id>' +
+    '    <title type="text">sugar</title>' +
+    '    <updated>2010-12-28T23:09:54Z</updated>' +
+    '    <author>' +
+    '      <name />' +
+    '    </author>' +
+    '    <link rel="edit" title="Food" href="Foods(1)" />' +
+    '    <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(1)/Category" />' +
+    '    <category term="DataJS.Tests.V2.Food" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+    '    <content type="application/xml">' +
+    '      <m:properties>' +
+    '        <d:FoodID m:type="Edm.Int32">1</d:FoodID>' +
+    '        <d:Name>sugar</d:Name>' +
+    '        <d:ServingSize m:type="Edm.Decimal">1</d:ServingSize>' +
+    '        <d:MeasurementUnit>tsp</d:MeasurementUnit>' +
+    '        <d:ProteinGrams m:type="Edm.Byte">0</d:ProteinGrams>' +
+    '        <d:FatGrams m:type="Edm.Int16">0</d:FatGrams>' +
+    '        <d:CarbohydrateGrams m:type="Edm.Int32">4</d:CarbohydrateGrams>' +
+    '        <d:CaloriesPerServing m:type="Edm.Int64">16</d:CaloriesPerServing>' +
+    '        <d:IsAvailable m:type="Edm.Boolean">false</d:IsAvailable>' +
+    '        <d:ExpirationDate m:type="Edm.DateTime">2011-12-28T00:00:00</d:ExpirationDate>' +
+    '        <d:ItemGUID m:type="Edm.Guid">ffffffff-ffff-ffff-ffff-ffffffffffff</d:ItemGUID>' +
+    '        <d:Weight m:type="Edm.Single">0.1</d:Weight>' +
+    '        <d:AvailableUnits m:type="Edm.SByte">0</d:AvailableUnits>' +
+    '        <d:Packaging m:type="DataJS.Tests.V2.Package">' +
+    '          <d:Type xml:space="preserve"> </d:Type>' +
+    '          <d:Color>BLUE</d:Color>' +
+    '          <d:NumberPerPackage m:type="Edm.Int32">-2147483648</d:NumberPerPackage>' +
+    '          <d:RequiresRefridgeration m:type="Edm.Boolean">true</d:RequiresRefridgeration>' +
+    '          <d:ShipDate m:type="Edm.DateTime">0001-01-01T00:00:00</d:ShipDate>' +
+    '          <d:PackageDimensions m:type="DataJS.Tests.V2.Dimensions">' +
+    '            <d:Length m:type="Edm.Decimal">-79228162514264337593543950335</d:Length>' +
+    '            <d:Height m:type="Edm.Int16">-32768</d:Height>' +
+    '            <d:Width m:type="Edm.Int64">-9223372036854775808</d:Width>' +
+    '            <d:Volume m:type="Edm.Double">-1.7976931348623157E+308</d:Volume>' +
+    '          </d:PackageDimensions>' +
+    '        </d:Packaging>' +
+    '        <d:CookedSize m:type="DataJS.Tests.V2.CookedDimensions" m:null="true" />' +
+    '      </m:properties>' +
+    '    </content>' +
+    '    <cooked:cooked cooked:length="" cooked:height="" cooked:width="" xmlns:cooked="http://www.example.org/cooked/">' +
+    '      <cooked:volume></cooked:volume>' +
+    '    </cooked:cooked>' +
+    '    <pr:price pr:value="0.2" xmlns:pr="http://www.example.org/price/"></pr:price>' +
+    '  </entry>' +
+    '</feed>';
+
+    var foodServiceV2MetadataText = '' +
+    '<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">' +
+    '  <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="2.0">' +
+    '    <Schema Namespace="DataJS.Tests.V2" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns="http://schemas.microsoft.com/ado/2007/05/edm">' +
+    '      <EntityType Name="Category">' +
+    '        <Key>' +
+    '          <PropertyRef Name="CategoryID" />' +
+    '        </Key>' +
+    '        <Property Name="CategoryID" Type="Edm.Int32" Nullable="false" />' +
+    '        <Property Name="Name" Type="Edm.String" Nullable="true" m:FC_TargetPath="SyndicationTitle" m:FC_ContentKind="text" m:FC_KeepInContent="true" />' +
+    '        <NavigationProperty Name="Foods" Relationship="DataJS.Tests.V2.Category_Foods" FromRole="Category" ToRole="Foods" />' +
+    '      </EntityType>' +
+    '      <EntityType Name="PreparedFood" BaseType="DataJS.Tests.V2.Food">' +
+    '        <Property Name="Instructions" Type="Edm.String" Nullable="true" />' +
+    '        <Property Name="NumberOfIngredients" Type="Edm.Single" Nullable="false" />' +
+    '      </EntityType>' +
+    '      <EntityType Name="Food">' +
+    '        <Key>' +
+    '          <PropertyRef Name="FoodID" />' +
+    '        </Key>' +
+    '        <Property Name="FoodID" Type="Edm.Int32" Nullable="false" />' +
+    '        <Property Name="Name" Type="Edm.String" Nullable="true" m:FC_TargetPath="SyndicationTitle" m:FC_ContentKind="text" m:FC_KeepInContent="true" />' +
+    '        <Property Name="UnitPrice" Type="Edm.Double" Nullable="false" m:FC_TargetPath="price/@value" m:FC_NsUri="http://www.example.org/price/" m:FC_NsPrefix="pr" m:FC_KeepInContent="false" />' +
+    '        <Property Name="ServingSize" Type="Edm.Decimal" Nullable="false" />' +
+    '        <Property Name="MeasurementUnit" Type="Edm.String" Nullable="true" />' +
+    '        <Property Name="ProteinGrams" Type="Edm.Byte" Nullable="false" />' +
+    '        <Property Name="FatGrams" Type="Edm.Int16" Nullable="false" />' +
+    '        <Property Name="CarbohydrateGrams" Type="Edm.Int32" Nullable="false" />' +
+    '        <Property Name="CaloriesPerServing" Type="Edm.Int64" Nullable="false" />' +
+    '        <Property Name="IsAvailable" Type="Edm.Boolean" Nullable="false" />' +
+    '        <Property Name="ExpirationDate" Type="Edm.DateTime" Nullable="false" />' +
+    '        <Property Name="ItemGUID" Type="Edm.Guid" Nullable="false" />' +
+    '        <Property Name="Weight" Type="Edm.Single" Nullable="false" />' +
+    '        <Property Name="AvailableUnits" Type="Edm.SByte" Nullable="false" />' +
+    '        <Property Name="Packaging" Type="DataJS.Tests.V2.Package" Nullable="false" />' +
+    '        <Property Name="CookedSize" Type="DataJS.Tests.V2.CookedDimensions" Nullable="false" m:FC_TargetPath="cooked/volume" m:FC_NsUri="http://www.example.org/cooked/" m:FC_NsPrefix="cooked" m:FC_SourcePath="Volume" m:FC_KeepInContent="false" m:FC_TargetPath_1="cooked/@width" m:FC_NsUri_1="http://www.example.org/cooked/" m:FC_NsPrefix_1="cooked" m:FC_SourcePath_1="Width" m:FC_KeepInContent_1="false" m:FC_TargetPath_2="cooked/@height" m:FC_NsUri_2="http://www.example.org/cooked/" m:FC_NsPrefix_2="cooked" m:FC_SourcePath_2="Height" m:FC_KeepInContent_2="false" m:FC_TargetPath_3="cooked/@length" m:FC_NsUri_3="http://www.example.org/cooked/" m:FC_NsPrefix_3="cooked" m:FC_SourcePath_3="Length" m:FC_KeepInContent_3="false" />' +
+    '        <NavigationProperty Name="Category" Relationship="DataJS.Tests.V2.Food_Category" FromRole="Food" ToRole="Category" />' +
+    '      </EntityType>' +
+    '      <ComplexType Name="Package">' +
+    '        <Property Name="Type" Type="Edm.String" Nullable="true" />' +
+    '        <Property Name="Color" Type="Edm.String" Nullable="true" />' +
+    '        <Property Name="NumberPerPackage" Type="Edm.Int32" Nullable="false" />' +
+    '        <Property Name="RequiresRefridgeration" Type="Edm.Boolean" Nullable="false" />' +
+    '        <Property Name="ShipDate" Type="Edm.DateTime" Nullable="false" />' +
+    '        <Property Name="PackageDimensions" Type="DataJS.Tests.V2.Dimensions" Nullable="false" />' +
+    '      </ComplexType>' +
+    '      <ComplexType Name="Dimensions">' +
+    '        <Property Name="Length" Type="Edm.Decimal" Nullable="false" />' +
+    '        <Property Name="Height" Type="Edm.Int16" Nullable="false" />' +
+    '        <Property Name="Width" Type="Edm.Int64" Nullable="false" />' +
+    '        <Property Name="Volume" Type="Edm.Double" Nullable="false" />' +
+    '      </ComplexType>' +
+    '      <ComplexType Name="CookedDimensions">' +
+    '        <Property Name="Length" Type="Edm.Decimal" Nullable="false" />' +
+    '        <Property Name="Height" Type="Edm.Int16" Nullable="false" />' +
+    '        <Property Name="Width" Type="Edm.Int64" Nullable="false" />' +
+    '        <Property Name="Volume" Type="Edm.Double" Nullable="false" />' +
+    '      </ComplexType>' +
+    '      <Association Name="Category_Foods">' +
+    '        <End Role="Category" Type="DataJS.Tests.V2.Category" Multiplicity="*" />' +
+    '        <End Role="Foods" Type="DataJS.Tests.V2.Food" Multiplicity="*" />' +
+    '      </Association>' +
+    '      <Association Name="Food_Category">' +
+    '        <End Role="Food" Type="DataJS.Tests.V2.Food" Multiplicity="*" />' +
+    '        <End Role="Category" Type="DataJS.Tests.V2.Category" Multiplicity="0..1" />' +
+    '      </Association>' +
+    '      <EntityContainer Name="FoodContainer" m:IsDefaultEntityContainer="true">' +
+    '        <EntitySet Name="Categories" EntityType="DataJS.Tests.V2.Category" />' +
+    '        <EntitySet Name="Foods" EntityType="DataJS.Tests.V2.Food" />' +
+    '        <AssociationSet Name="Category_Foods" Association="DataJS.Tests.Category_Foods">' +
+    '          <End Role="Category" EntitySet="Categories" />' +
+    '          <End Role="Foods" EntitySet="Foods" />' +
+    '        </AssociationSet>' +
+    '        <AssociationSet Name="Food_Category" Association="DataJS.Tests.V2.Food_Category">' +
+    '          <End Role="Food" EntitySet="Foods" />' +
+    '          <End Role="Category" EntitySet="Categories" />' +
+    '        </AssociationSet>' +
+    '        <FunctionImport Name="ResetData" m:HttpMethod="POST" />' +
+    '        <FunctionImport Name="FoodsAvailable" ReturnType="Collection(Edm.String)" m:HttpMethod="GET" />' +
+    '        <FunctionImport Name="PackagingTypes" ReturnType="Collection(DataJS.Tests.V2.Package)" m:HttpMethod="GET" />' +
+    '      </EntityContainer>' +
+    '    </Schema>' +
+    '  </edmx:DataServices>' +
+    '</edmx:Edmx>';
+
+    djstest.addTest(function applyEntryCustomizationToEntryTest() {
+        var metadata = parseMetadataHelper(customerSampleMetadataText);
+        var data = { __metadata: { type: "Ns.Customer" }, Name: "Name", LastName: "Last Name", Address: { Street: "Street Value", City: "City Value" }, FavoriteNumber: 123 };
+        var request = { data: data, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.write(request, { metadata: metadata });
+
+        djstest.assert(request.body !== null, "request.body !== null");
+        djstest.assert(request.body.indexOf("<a:summary type=\"xhtml\">Name</a:summary>") !== -1, 'request.body.indexOf("<a:summary>Name</a:summary>") !== -1');
+        djstest.assert(request.body.indexOf('baz="Last Name"') !== -1, 'request.body.indexOf(baz="Last Name") !== -1');
+        djstest.assert(request.body.indexOf('city="City Value"') !== -1, 'request.body.indexOf(city="City Value") !== -1');
+        djstest.assert(request.body.indexOf('<prefix:foo ') !== -1, "request.body.indexOf('<prefix:foo ') !== -1");
+        djstest.assert(request.body.indexOf('term="Ns.Customer"') !== -1, "request.body.indexOf(term='Ns.Customer') !== -1");
+        djstest.assert(request.body.indexOf('>123</') !== -1, "request.body.indexOf(>123</) !== -1");
+
+        // Try with other mapping types.
+        metadata.dataServices.schema[0].entityType[0].property[1].FC_ContentKind = "html";
+        request.body = undefined;
+        OData.atomHandler.write(request, { metadata: metadata });
+        djstest.assert(request.body.indexOf("<a:summary type=\"html\">Name</a:summary>") !== -1, 'request.body.indexOf("<a:summary type="html">Name</a:summary>") !== -1');
+
+        // Try with a null value now.
+        request.data.FavoriteNumber = null;
+        request.body = null;
+        OData.atomHandler.write(request, { metadata: metadata });
+        djstest.assert(request.body.indexOf('>123</') === -1, "request.body.indexOf(>123</) === -1");
+        djstest.assert(request.body.indexOf('m:null="true"') !== -1, "request.body.indexOf(m:null=true) !== -1");
+
+        // Try with a null complex type now.
+        request.data.FavoriteNumber = 123;
+        request.data.Address = null;
+        request.body = null;
+        OData.atomHandler.write(request, { metadata: metadata });
+        djstest.assert(request.body.indexOf('Street') === -1, "request.body.indexOf(Street) === -1");
+        djstest.assert(request.body.indexOf('m:null="true"') !== -1, "request.body.indexOf(m:null=true) !== -1");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function normalizeHeadersReadTest() {
+        // Verifies that headers are normalized for reading.
+        // See issue at http://datajs.codeplex.com/workitem/148
+        window.MockHttpClient.clear().addResponse("/foo", {
+            statusCode: 200,
+            body: foodServiceV2FoodsSampleText,
+            headers: { "Accept": "application/json", "unknown": "u", "content-type": "application/atom+xml", "dataserviceversion": "2.0" }
+        });
+
+        OData.read("/foo", function (data, response) {
+            djstest.assertAreEqual(data.results.length, 2, "data.results.length has two entries");
+            djstest.assertAreEqual(response.headers.Accept, "application/json", "Accept available");
+            djstest.assertAreEqual(response.headers.unknown, "u", "u unmodified");
+            djstest.assertAreEqual(response.headers["Content-Type"], "application/atom+xml", "Content-Type available");
+            djstest.assertAreEqual(response.headers["DataServiceVersion"], "2.0", "DataServiceVersion available");
+            djstest.done();
+        }, undefined, undefined, MockHttpClient);
+    });
+
+    djstest.addTest(function normalizeHeadersWriteTest() {
+        // Verifies that headers are normalized for writing.
+        // See issue at http://datajs.codeplex.com/workitem/148
+        window.MockHttpClient.clear().addRequestVerifier("/foo", function (request) {
+            djstest.assertAreEqual(request.headers["Content-Type"], "application/atom+xml", "json found");
+            djstest.assert(request.body.indexOf(":bar") !== -1, "format recognized and applied as XML");
+            djstest.done();
+        });
+
+        var request = {
+            method: "POST",
+            requestUri: "/foo",
+            data: { "bar": 123 },
+            headers: { "content-type": "application/atom+xml" }
+        };
+        OData.request(request, function (data) {
+        }, undefined, undefined, MockHttpClient);
+    });
+
+    djstest.addTest(function testParsePrimitivePropertiesBasic() {
+        var feed = "\
+        <entry xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+               xmlns:d=\'http://schemas.microsoft.com/ado/2007/08/dataservices\' \r\n\
+               xmlns:m=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+               xmlns:atom=\'http://www.w3.org/2005/Atom\' \r\n\
+               xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+               xmlns=\'http://www.w3.org/2005/Atom\'>\r\n\
+           <id>http://services.odata.org/OData/OData.svc/the id</id> \r\n \
+           <content type='application/xml'>\r\n \
+            <m:properties xmlns=\'http://schemas.microsoft.com/ado/2007/08/dataservices\'>\r\n \
+             <Boolean m:type='Edm.Boolean'>true</Boolean>\r\n \
+             <Binary m:type='Edm.Binary'>01007A8A680D9E14A64EAC1242DD33C9DB05</Binary>\r\n \
+             <Byte m:type='Edm.Byte'>8</Byte>\r\n \
+             <DateTime m:type='Edm.DateTime'>2010-11-01T15:13:25</DateTime>\r\n \
+             <Decimal m:type='Edm.Decimal'>100.10</Decimal>\r\n \
+             <Guid m:type='Edm.Guid'>12345678-aaaa-bbbb-cccc-ddddeeeeffff</Guid>\r\n \
+             <!-- <Time m:type='Edm.Time'>P05DT12H30M05.125S</Time> --> \r\n \
+             <DateTimeOffset m:type='Edm.DateTimeOffset'>2010-11-01T15:13:25+10:00</DateTimeOffset>\r\n \
+             <Double m:type='Edm.Double'>1E+10</Double>\r\n \
+             <Single m:type='Edm.Single'>100.01</Single>\r\n \
+             <Int16 m:type='Edm.Int16'>16</Int16>\r\n \
+             <Int32 m:type='Edm.Int32'>32</Int32>\r\n \
+             <Int64 m:type='Edm.Int64'>64</Int64>\r\n \
+             <SByte m:type='Edm.SByte'>-8</SByte>\r\n \
+            </m:properties>\r\n \
+           </content>\r\n \
+        </entry>\r\n";
+
+        var response = { body: feed, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, {});
+
+        djstest.assertsExpected(1);
+        ODataReadOracle.readEntryLoopback(feed, function (expectedData) {
+            djstest.assertAreEqualDeep(response.data, expectedData, "Verify deserialized data");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function deserializeCustomizationsNullAndXhtmlTest() {
+        var payload = "<entry " +
+        ' xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" \r\n' +
+        " xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' \r\n" +
+        " xmlns=\"http://www.w3.org/2005/Atom\">\r\n" +
+        " <id>http://localhost/tests/endpoints/FoodDataService.svc/Foods(1)</id> " +
+        " <author><name>Customer #1</name></author>" +
+        " <summary><b>Name</b></summary>" +
+        " <category term='Ns.Customer' scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme' /> " +
+        " <content type='application/xml'><m:properties><d:ID m:type='Edm.Int32'>1</d:ID>" +
+        " <d:LastName m:null='true' /></m:properties></content>" +
+        "</entry>";
+        var metadata = parseMetadataHelper(customerSampleMetadataText);
+        var response = { body: payload, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, { metadata: metadata });
+
+        djstest.assertAreEqual(response.data.LastName, null, "last name is null");
+        djstest.assertAreEqual(response.data.Name, "<b xmlns=\"http://www.w3.org/2005/Atom\">Name</b>", "name includes tags");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function parseCustomizationSampleTest() {
+        var payload = foodServiceV2FoodsSampleText;
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        var response = { body: payload, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, { metadata: metadata });
+
+        djstest.assert(response.data !== null, "response.data !== null");
+        djstest.assert(response.data.results !== null, "response.data.results !== null");
+
+        var r = response.data.results;
+        djstest.assertAreEqual(r[0].__metadata.type, "DataJS.Tests.V2.Food", "r[0].__metadata.type");
+        djstest.assertAreEqual(r[0].Name, "flour", "r[0].Name");
+        djstest.assertAreEqual(r[0].UnitPrice, 0.19999, "r[0].UnitPrice");
+        djstest.assertAreEqual(r[0].ServingSize, 1, "r[0].ServingSize");
+
+        // CONSIDER: we intended to have complex type have their type annotation out-of-band, but JSON has it in-line; do we want to normalize this out everywhere?
+        // djstest.assertAreEqual(r[0].Packaging.__metadata.type, "DataJS.Tests.PackageV2", "r[0].Packaging.__metadata.type");
+        djstest.assertAreEqual(r[0].Packaging.Type, null, "package type for flour is null");
+        djstest.assertAreEqual(r[0].Packaging.PackageDimensions.Height, 32767, "r[0].Packaging.PackageDimensions.Height");
+
+        djstest.assertAreEqual(r[0].CookedSize.Length, 2, "r[0].CookedSize.Length");
+        djstest.assertAreEqual(r[0].CookedSize.Volume, 6, "r[0].CookedSize.Volume");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function parseIntoPropertiesTest() {
+        var payload = '' +
+            '<entry xml:base="http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">' +
+            '  <id>http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/Foods(0)</id>' +
+            '  <title type="text">flour</title>' +
+            '  <updated>2010-12-28T23:09:54Z</updated>' +
+            '  <author><name /></author>' +
+            '  <link rel="edit" title="Food" href="Foods(0)" />' +
+            '  <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(0)/Category" />' +
+            '  <category term="DataJS.Tests.V2.Food" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+            '  <content type="application/xml">' +
+            '    <m:properties>' +
+            '      <d:FoodID m:type="Edm.Int32">0</d:FoodID>' +
+            '      <d:Name>flour</d:Name>' +
+            '      <d:ServingSize m:type="Edm.Decimal">1</d:ServingSize>' +
+            '      <d:MeasurementUnit>Cup</d:MeasurementUnit>' +
+            '      <d:ProteinGrams m:type="Edm.Byte">3</d:ProteinGrams>' +
+            '      <d:FatGrams m:type="Edm.Int16">1</d:FatGrams>' +
+            '      <d:CarbohydrateGrams m:type="Edm.Int32">20</d:CarbohydrateGrams>' +
+            '      <d:CaloriesPerServing m:type="Edm.Int64">140</d:CaloriesPerServing>' +
+            '      <d:IsAvailable m:type="Edm.Boolean">true</d:IsAvailable>' +
+            '      <d:ExpirationDate m:type="Edm.DateTime">2010-12-25T12:00:00</d:ExpirationDate>' +
+            '      <d:ItemGUID m:type="Edm.Guid">27272727-2727-2727-2727-272727272727</d:ItemGUID>' +
+            '      <d:Weight m:type="Edm.Single">10</d:Weight>' +
+            '      <d:AvailableUnits m:type="Edm.SByte">1</d:AvailableUnits>' +
+            '      <d:Packaging m:type="DataJS.Tests.V2.Package">' +
+            '        <d:Type m:null="true" />' +
+            '        <d:Color></d:Color>' +
+            '        <d:NumberPerPackage m:type="Edm.Int32">2147483647</d:NumberPerPackage>' +
+            '        <d:RequiresRefridgeration m:type="Edm.Boolean">false</d:RequiresRefridgeration>' +
+            '        <d:ShipDate m:type="Edm.DateTime">0001-01-01T00:00:00</d:ShipDate>' +
+            '        <d:PackageDimensions m:type="DataJS.Tests.V2.Dimensions">' +
+            '          <d:Length m:type="Edm.Decimal">79228162514264337593543950335</d:Length>' +
+            '          <d:Height m:type="Edm.Int16">32767</d:Height>' +
+            '          <d:Width m:type="Edm.Int64">9223372036854775807</d:Width>' +
+            '          <d:Volume m:type="Edm.Double">1.7976931348623157E+308</d:Volume>' +
+            '        </d:PackageDimensions>' +
+            '      </d:Packaging>' +
+            '    </m:properties>' +
+            '  </content>' +
+            '  <cooked:cooked cooked:length="2" cooked:height="1" cooked:width="3" xmlns:cooked="http://www.example.org/cooked/">' +
+            '    <cooked:volume>6</cooked:volume>' +
+            '  </cooked:cooked>' +
+            '  <pr:price pr:value="0.19999" xmlns:pr="http://www.example.org/price/"></pr:price>' +
+            '</entry>';
+
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        var response = { body: payload, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, { metadata: metadata });
+
+        djstest.assert(response.data !== null, "response.data !== null");
+        djstest.assertAreEqual(response.data.__metadata.type, "DataJS.Tests.V2.Food", "types match");
+        djstest.assertAreEqual(response.data.UnitPrice, 0.19999, "Price is as expected");
+        djstest.assertAreEqual(response.data.__metadata.properties.UnitPrice.type, "Edm.Double", "Price was marked as a double");
+
+        djstest.assertAreEqual(response.data.CookedSize.__metadata.properties.Length.type, "Edm.Decimal", "CookedSize.Length was marked as a decimal");
+
+        // When properties are marked on complex type metadata, this assertion will be true as well.
+        // djstest.assertAreEqual(response.data.Packaging.__metadata.Type.type, "Edm.String", "Packaging type was marked as a string");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function parseNullInlineTest() {
+        // Shorter version of:
+        // OData.read("/tests/endpoints/FoodStoreDataService.svc/Foods?$expand=Category&$filter=Category eq null", function(data, response) {
+
+        var body = '' +
+        '<feed xml:base="http://localhost/tests/endpoints/FoodStoreDataService.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">' +
+        '  <entry>' +
+        '    <id>http://localhost/tests/endpoints/FoodStoreDataService.svc/Foods(2)</id>' +
+        '    <link rel="edit" title="Food" href="Foods(2)" />' +
+        '    <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(2)/Category">' +
+        '      <m:inline />' +
+        '    </link>' +
+        '    <category term="DataJS.Tests.Food" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+        '    <content type="application/xml">' +
+        '      <m:properties>' +
+        '        <d:FoodID m:type="Edm.Int32">2</d:FoodID>' +
+        '        <d:Name>1 Chicken Egg</d:Name>' +
+        '        <d:UnitPrice m:type="Edm.Double">0.55</d:UnitPrice>' +
+        '      </m:properties>' +
+        '    </content>' +
+        '  </entry>' +
+        '</feed>';
+        var response = { body: body, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, {});
+        var data = response.data;
+        var r = data.results[0];
+        djstest.assertAreEqual(r.Category, null, "r.Category is null as an empty inline entry");
+        djstest.assertAreEqual(r.FoodID, 2, "r.FoodID read correctly");
+        djstest.assertAreEqual(r.__metadata.properties.Category.extensions[0].name, "title", "Category title extension parsed");
+        djstest.assertAreEqual(r.__metadata.properties.Category.extensions[0].value, "Category", "Category title value parsed");
+        djstest.done();
+    });
+
+    djstest.addTest(function serializeEpmTest() {
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        var data = { __metadata: { type: "DataJS.Tests.V2.Food" }, FoodID: 123, Name: "name", CookedSize: { Length: 1, Height: 2, Width: 3, Volume: 4} };
+        var request = { data: data, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.write(request, { metadata: metadata });
+        djstest.assert(request.body.indexOf("CookedSize") === -1, "CookedSize element is missing from payload");
+        djstest.assert(request.body.indexOf("length=") !== -1, "length is available as a mapped attribute");
+        djstest.assert(request.body.indexOf("height=") !== -1, "height is available as a mapped attribute");
+        djstest.assert(request.body.indexOf("width=") !== -1, "width is available as a mapped attribute");
+        djstest.assert(request.body.indexOf("volume>") !== -1, "volume is available as a mapped element");
+        djstest.done();
+    });
+
+    djstest.addTest(function writeNullComplexTypeTest() {
+
+        // Verify that the server can be updated to set a complex value to null.
+        var foodSetUrl = "./endpoints/FoodStoreDataServiceV2.svc/Foods";
+        resetFoodData();
+
+        // Without metadata, this will fail because the server version won't be set to 2.0.
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        OData.read(foodSetUrl + "?$top=1", function (data) {
+            var item = data.results[0];
+            djstest.assert(item.Packaging, "item.Packaging is not null");
+            item.Packaging = null;
+
+            // The server will reject links for PUT operations.
+            delete item.Category;
+
+            OData.request({ method: "PUT", requestUri: item.__metadata.uri, data: item, headers: { "Content-Type": "application/atom+xml"} }, function (data) {
+                // Re-read the item.
+                OData.read(item.__metadata.uri, function (data) {
+                    djstest.assert(data, "data was read successfully again");
+                    djstest.assert(!data.Packaging, "!data.Packaging");
+                    resetFoodData();
+                    djstest.done();
+                }, djstest.failAndDoneCallback("Failed to read back food.", resetFoodData));
+            }, djstest.failAndDoneCallback("Failed to write food"), null, null, metadata);
+        }, djstest.failAndDoneCallback("Failed to read food"), null, null, metadata);
+    });
+
+    djstest.addTest(function writeNonNullLinkTest() {
+        // Verify that the server can be updated to set a link to null.
+        resetFoodData();
+        var foodSetUrl = "./endpoints/FoodStoreDataServiceV2.svc/Foods";
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        OData.read(foodSetUrl + "?$top=1", function (data) {
+            var item = data.results[0];
+
+            // Turn this into something different.
+            delete item.__metadata.uri;
+            item.FoodID = 1001;
+
+            OData.request({ method: "POST", requestUri: foodSetUrl, data: item, headers: { "Content-Type": "application/atom+xml"} }, function (data) {
+                // Re-read the item.
+                OData.read(data.__metadata.uri + "?$expand=Category", function (data) {
+                    djstest.assert(data, "data was read successfully again");
+                    djstest.assert(data.Category, "data.Category");
+                    djstest.assert(data.Category.Name, "data.Category.Name");
+                    resetFoodData();
+                    djstest.done();
+                }, djstest.failAndDoneCallback("Failed to read back food.", resetFoodData));
+            }, djstest.failAndDoneCallback("Failed to add modified food"), null, null, metadata);
+        }, djstest.failAndDoneCallback("Failed to read food"), null, null, metadata);
+    });
+
+    djstest.addTest(function writeNullLinkTest() {
+        // Verify that the server can be updated to set a link to null.
+        resetFoodData();
+        var foodSetUrl = "./endpoints/FoodStoreDataServiceV2.svc/Foods";
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        OData.read(foodSetUrl + "?$top=1", function (data) {
+            var item = data.results[0];
+
+            // Turn this into something different.
+            delete item.__metadata.uri;
+            item.FoodID = 1001;
+            item.Category = null;
+
+            OData.request({ method: "POST", requestUri: foodSetUrl, data: item, headers: { "Content-Type": "application/atom+xml"} }, function (data) {
+                // Re-read the item.
+                OData.read(data.__metadata.uri + "?$expand=Category", function (data) {
+                    djstest.assert(data, "data was read successfully again");
+                    djstest.assert(!data.Category, "data.Category");
+                    resetFoodData();
+                    djstest.done();
+                }, djstest.failAndDoneCallback("Failed to read back food.", resetFoodData));
+            }, djstest.failAndDoneCallback("Failed to add modified food"), null, null, metadata);
+        }, djstest.failAndDoneCallback("Failed to read food"), null, null, metadata);
+    });
+
+    // DATAJS INTERNAL START
+    djstest.addTest(function lookupEntityTypeInSchemaTest() {
+        var schemaEmpty = {};
+        var schemaZero = {
+            namespace: "Zero",
+            entityType: [
+                { name: "Genre" },
+                { name: "Language" }
+            ]
+        };
+        var schemaOne = {
+            namespace: "One",
+            entityType: [
+                { name: "Genre1" },
+                { name: "Language1" }
+            ]
+        };
+        var edmx = { dataServices: { schema: [schemaEmpty, schemaZero, schemaOne]} };
+
+        var lookupEntityTypeInSchema = function (name, schema) {
+            return OData.lookupInMetadata(name, schema, "entityType");
+        };
+
+        djstest.assertAreEqual(
+        lookupEntityTypeInSchema("Zero.Genre"),
+        null, "Expected null for missing metadata");
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("", schemaEmpty),
+            null, "Expected null for empty type name");
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("FooWar", schemaEmpty),
+            null, "Expected null for mismatched name/namespace");
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("Zero", schemaZero),
+            null, "Expected null for unqualified type name");
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("Zero.Something", schemaZero),
+            null, "Expected null for mismatched type name");
+        djstest.assertAreEqualDeep(
+            lookupEntityTypeInSchema("Zero.Genre", schemaZero),
+            { name: "Genre" }, "Found type by full name");
+
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("Zero.Something", edmx),
+            null, "Expected null for mismatched type name in edmx");
+        djstest.assertAreEqualDeep(
+            lookupEntityTypeInSchema("Zero.Genre", edmx),
+            { name: "Genre" }, "Found type by full name in edmx");
+        djstest.assertAreEqualDeep(
+            lookupEntityTypeInSchema("One.Genre1", edmx),
+            { name: "Genre1" }, "Found type by full name in edmx");
+
+        djstest.assertAreEqual(
+            OData.lookupInMetadata("One.Genre1", edmx, "complexType"),
+            null, "Expected null for a complex type lookup of an entity type.");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testLookupEntityType() {
+        var schemaZero = {
+            namespace: "Zero",
+            entityType: [
+                { name: "Genre" },
+                { name: "Language" }
+            ]
+        };
+        var schemaOne = {
+            namespace: "One",
+            entityType: [
+                { name: "Genre1" },
+                { name: "Language1" }
+            ]
+        };
+        var schemaTwo = {
+            namespace: "Two",
+            entityType: [
+                { name: "Genre2" },
+                { name: "Language2" }
+            ]
+        };
+        var edmx = { dataServices: { schema: [schemaZero, schemaOne]} };
+        var metadata = [edmx, schemaTwo];
+
+        djstest.assertAreEqual(
+            OData.lookupEntityType("Zero.Something", metadata),
+            null, "Expected null for mismatched type name in metadata");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("Zero.Genre", null),
+            null, "Expected null for missing metadata");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType(null, metadata),
+            null, "Expected null for missing name");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("Zero.Genre", metadata),
+            { name: "Genre" }, "Found type by full name in metadata");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("One.Genre1", metadata),
+            { name: "Genre1" }, "Found type by full name in metadata");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("One.Genre1", edmx),
+            { name: "Genre1" }, "Found type by full name in edmx");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("Two.Genre2", metadata),
+            { name: "Genre2" }, "Found type by full name in metadata");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testParseSimpleServiceDocument() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <collection href=\"Products\">\r\n\
+                <atom:title>Products</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Categories\">\r\n\
+                <atom:title>Categories</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Suppliers\">\r\n\
+                <atom:title>Suppliers</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 1, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        djstest.assertAreEqual(workspace.title, "Default", "Incorrect service doc title");
+
+        var expectedCollections = [
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Products", expectedTitle: "Products" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Categories", expectedTitle: "Categories" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Suppliers", expectedTitle: "Suppliers" }
+        ];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        var i, len;
+        for (i = 0, len = expectedCollections.length; i < len; i++) {
+            djstest.assertAreEqual(workspace.collections[i].href, expectedCollections[i].expectedHref, "Incorrect href on collection");
+            djstest.assertAreEqual(workspace.collections[i].title, expectedCollections[i].expectedTitle, "Incorrect title on collection");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testServiceDocMustHaveAtLeastOneWorkspaceElement() {
+        // Construct a service doc with no workspaces and verify that the parser throws.
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+         </service>\r\n";
+
+        djstest.expectException(function () {
+            var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+        }, "Parsing service doc with no workspaces");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testServiceDocMayHaveMoreThanOneWorkspaceElement() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <collection href=\"Products\">\r\n\
+                <atom:title>Products</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Categories\">\r\n\
+                <atom:title>Categories</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Suppliers\">\r\n\
+                <atom:title>Suppliers</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+            <workspace>\r\n\
+              <atom:title>Second Workspace</atom:title> \r\n\
+              <collection href=\"Collection1\">\r\n\
+                <atom:title>Collection Number 1</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Collection2\">\r\n\
+                <atom:title>Collection Number 2</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 2, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        djstest.assertAreEqual(workspace.title, "Default", "Incorrect service doc title");
+
+        var expectedCollections;
+        expectedCollections = [
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Products", expectedTitle: "Products" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Categories", expectedTitle: "Categories" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Suppliers", expectedTitle: "Suppliers" }
+        ];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        var i, len;
+        for (i = 0, len = expectedCollections.length; i < len; i++) {
+            djstest.assertAreEqual(workspace.collections[i].href, expectedCollections[i].expectedHref, "Incorrect href on collection");
+            djstest.assertAreEqual(workspace.collections[i].title, expectedCollections[i].expectedTitle, "Incorrect title on collection");
+        }
+
+        workspace = serviceDoc.workspaces[1];
+        djstest.assertAreEqual(workspace.title, "Second Workspace", "Incorrect service doc title");
+
+        expectedCollections = [
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Collection1", expectedTitle: "Collection Number 1" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Collection2", expectedTitle: "Collection Number 2" }
+        ];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        for (i = 0, len = expectedCollections.length; i < len; i++) {
+            djstest.assertAreEqual(workspace.collections[i].href, expectedCollections[i].expectedHref, "Incorrect href on collection");
+            djstest.assertAreEqual(workspace.collections[i].title, expectedCollections[i].expectedTitle, "Incorrect title on collection");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testCollectionTitlesAndHrefsMayBeDifferent() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <collection href=\"abc\">\r\n\
+                <atom:title>xyz</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"blah\">\r\n\
+                <atom:title>foo</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 1, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        djstest.assertAreEqual(workspace.title, "Default", "Incorrect service doc title");
+
+        var expectedCollections = [
+            { expectedHref: "http://services.odata.org/OData/OData.svc/abc", expectedTitle: "xyz" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/blah", expectedTitle: "foo" }
+        ];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        var i, len;
+        for (i = 0, len = expectedCollections.length; i < len; i++) {
+            djstest.assertAreEqual(workspace.collections[i].href, expectedCollections[i].expectedHref, "Incorrect href on collection");
+            djstest.assertAreEqual(workspace.collections[i].title, expectedCollections[i].expectedTitle, "Incorrect title on collection");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testParserShouldTreatMissingWorkspaceTitleAsBlank() {
+        // Per RFC 5023 Section 8.3.2.1, the workspace element MUST have a title but
+        // in the interests of being permissive, we should treat this as blank.
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <!-- No workspace title element -->\r\n\
+              <collection href=\"Products\">\r\n\
+                <atom:title>Products</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Categories\">\r\n\
+                <atom:title>Categories</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Suppliers\">\r\n\
+                <atom:title>Suppliers</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 1, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        djstest.assertAreEqual(workspace.title, "", "Incorrect service doc title");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testWorkspaceMayHaveNoCollections() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 1, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        var expectedCollections = [];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testCollectionMustHaveTitleElement() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <collection href=\"Products\">\r\n\
+                <!-- No title element -->\r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        djstest.expectException(function () {
+            var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+        }, "Parsing service doc with a collection with no title element");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testCollectionMustHaveHrefAttribute() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <!-- href attribute missing below --> \r\n\
+              <collection>\r\n\
+                <atom:title>Products</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        djstest.expectException(function () {
+            var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+        }, "Parsing service doc with a collection with no href attribute");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadDocumentTest() {
+        var emptyServiceString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+          <workspace>\r\n\
+            <atom:title>empty service</atom:title> \r\n\
+          </workspace>\r\n\
+        </service>\r\n";
+
+        var emptyFeedString = "\
+        <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>feed id</id> \r\n\
+           <title>empty feed</title> \r\n\
+        </feed> \r\n";
+
+        var emptyEntryString = "\
+        <entry xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>entry id</id> \r\n\
+           <title>empty entry</title> \r\n\
+        </entry> \r\n";
+
+        var nonAtomString = "\
+        <notAtom xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>entry id</id> \r\n\
+           <title>empty entry</title> \r\n\
+        </notAtom> \r\n";
+
+        var service = OData.atomReadDocument(datajs.xmlParse(emptyServiceString).documentElement);
+        var feed = OData.atomReadDocument(datajs.xmlParse(emptyFeedString).documentElement);
+        var entry = OData.atomReadDocument(datajs.xmlParse(emptyEntryString).documentElement);
+        var nonAtom = OData.atomReadDocument(datajs.xmlParse(nonAtomString).documentElement);
+
+        djstest.assert(service && service.workspaces.length === 1, "atomReadDocument deserialized a service document");
+        djstest.assert(feed && feed.results.length === 0, "atomReadDocument deserialized a feed document");
+        djstest.assert(entry && !entry.results && entry.__metadata.uri === "http://services.odata.org/OData/OData.svc/entry id", "atomReadDocument deserialized a entry document");
+        djstest.assertAreEqual(nonAtom, undefined, "atomReadDocument returns undefined with non Atom input");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadFeedV1Test() {
+        var simpleFeedString = "\
+        <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns:m=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>feed id</id> \r\n\
+           <title>test feed</title> \r\n\
+           <entry> \r\n\
+             <id>entry id</id> \r\n\
+             <title>empty entry</title> \r\n\
+           </entry> \r\n\
+        </feed> \r\n";
+
+        var expectedFeed = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/feed id",
+                uri_extensions: [],
+                title: "test feed",
+                title_extensions: [],
+                feed_extensions: []
+            },
+            results: [
+                { __metadata: {
+                    uri: "http://services.odata.org/OData/OData.svc/entry id",
+                    uri_extensions: []
+                }
+                }
+           ]
+        };
+
+        var feed = OData.atomReadFeed(datajs.xmlParse(simpleFeedString).documentElement);
+
+        djstest.assert(feed, "atomReadFeed didn't return a feed object for a V1 payload");
+        djstest.assertAreEqualDeep(feed, expectedFeed, "atomReadFeed didn't return the expected feed");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadFeedV2Test() {
+        var simpleFeedString = "\
+        <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns:m=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>feed id</id> \r\n\
+           <title>test feed</title> \r\n\
+           <m:count>2</m:count> \r\n\
+           <entry> \r\n\
+             <id>entry id</id> \r\n\
+             <title>empty entry</title> \r\n\
+           </entry> \r\n\
+           <link rel=\'next\' href=\'http://nexturi\'/> \r\n\
+        </feed> \r\n";
+
+        var expectedFeed = {
+            __count: 2,
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/feed id",
+                uri_extensions: [],
+                title: "test feed",
+                title_extensions: [],
+                feed_extensions: [],
+                next_extensions: []
+            },
+            __next: "http://nexturi",
+            results: [
+                { __metadata: {
+                    uri: "http://services.odata.org/OData/OData.svc/entry id",
+                    uri_extensions: []
+                }
+                }
+           ]
+        };
+
+        var feed = OData.atomReadFeed(datajs.xmlParse(simpleFeedString).documentElement);
+
+        djstest.assert(feed, "atomReadFeed didn't return a feed object for a V2 payload");
+        djstest.assertAreEqualDeep(feed, expectedFeed, "atomReadFeed didn't return the expected feed");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadFeedWithActionsAndFunctionsTest() {
+        var feed = "\r\n\
+        <feed xml:base='http://services.odata.org/OData/OData.svc/' \r\n\
+              xmlns:app='http://www.w3.org/2007/app' \r\n\
+              xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' \r\n\
+              xmlns:me='http://myExtensions' \r\n\
+              xmlns='http://www.w3.org/2005/Atom'> \r\n\
+           <id>feed id</id> \r\n\
+           <title>test feed</title> \r\n\
+           <m:action metadata='#EntityContainer.Action1' title='Action1' target='http://service/entities(0)/action' /> \r\n\
+           <m:action metadata='#EntityContainer.Action2' title='Action2' target='entities(0)/action2'/> \r\n\
+           <m:action metadata='http://someService/$metadata#Container.Action1' title='Action1' target='http://someService/action' /> \r\n\
+           <m:function metadata='#EntityContainer.Function1' title='Function1' target='http://service/entities(0)/function' /> \r\n\
+           <m:function metadata='#EntityContainer.Function2' title='Function2' target='entities(0)/function2' /> \r\n\
+           <m:function metadata='http://someService/$metadata#Container.Function1' title='Function1' target='http://someService/function' /> \r\n\
+        </feed> \r\n";
+
+        var expected = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/feed id",
+                uri_extensions: [],
+                title: "test feed",
+                title_extensions: [],
+                feed_extensions: [],
+                actions: [
+                    {
+                        metadata: "#EntityContainer.Action1",
+                        title: "Action1",
+                        target: "http://service/entities(0)/action",
+                        extensions: []
+                    },
+                    {
+                        metadata: "#EntityContainer.Action2",
+                        title: "Action2",
+                        target: "http://services.odata.org/OData/OData.svc/entities(0)/action2",
+                        extensions: []
+                    },
+                    {
+                        metadata: "http://someService/$metadata#Container.Action1",
+                        title: "Action1",
+                        target: "http://someService/action",
+                        extensions: []
+                    }
+                ],
+                functions: [
+                    {
+                        metadata: "#EntityContainer.Function1",
+                        title: "Function1",
+                        target: "http://service/entities(0)/function",
+                        extensions: []
+                    },
+                    {
+                        metadata: "#EntityContainer.Function2",
+                        title: "Function2",
+                        target: "http://services.odata.org/OData/OData.svc/entities(0)/function2",
+                        extensions: []
+                    },
+                    {
+                        metadata: "http://someService/$metadata#Container.Function1",
+                        title: "Function1",
+                        target: "http://someService/function",
+                        extensions: []
+                    }
+                ]
+            },
+            results: []
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" }, body: feed };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, expected, "atomReadEntry didn't return the expected entry object");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadFeedExtensionsTest() {
+        var feedWithExtensionsString = "\
+         <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns:me=\'http://myExtensions' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\' attr1=\'a1\' me:attr2=\'a2\'> \r\n\
+           <me:element1>e1</me:element1> \r\n\
+           <me:element2> \r\n\
+               <me:element21 attr3=\'a3\' me:attr4=\'a4\' >e1</me:element21> \r\n\
+           </me:element2> \r\n\
+           <id>feed id</id> \r\n\
+           <title>test feed</title> \r\n\
+        </feed> \r\n"
+
+        var feed = OData.atomReadFeed(datajs.xmlParse(feedWithExtensionsString).documentElement);
+        djstest.assert(feed, "atomReadFeed didn't return a feed object for a payload with feed extensions");
+        djstest.assertAreEqual(feed.__metadata.feed_extensions.length, 4, "atomReadFeed didn't return the expected number of extensions");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadFeedLinksTest() {
+        var feedLinksString = "\
+        <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns:me=\'http://myExtensions\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+          <link rel=\'next\' href=\'http://nexturi\' me:attr1=\'a1\' attr2=\'a2\'/> \r\n\
+          <link rel=\'self\' href=\'http://selfuri\' me:attr3=\'a1\' attr4=\'a2\'/> \r\n\
+          <link rel=\'alternate\' href=\'http://otheruri\'/> \r\n\
+        </feed> \r\n";
+
+        var root = datajs.xmlParse(feedLinksString).documentElement;
+        var feed = { __metadata: {} };
+        datajs.xmlChildElements(root, function (child) {
+            OData.atomReadFeedLink(child, feed);
+        });
+
+        djstest.assertAreEqual(feed.__next, "http://nexturi", "atomReadFeedLink didn't read the next link element");
+        djstest.assertAreEqual(feed.__metadata.next_extensions.length, 2, "atomReadFeedLink didn't return the expected number of next link extensions");
+        djstest.assertAreEqual(feed.__metadata.self, "http://selfuri", "atomReadFeedLink didn't read the self link element");
+        djstest.assertAreEqual(feed.__metadata.self_extensions.length, 2, "atomReadFeedLink didn't return the expected number of self link extensions");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadLinkTest() {
+        var linkString = "\
+        <link xmlns:me=\'http://myExtensions\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\' \r\n\
+              rel=\'next\' \r\n\
+              href=\'http://nexturi\' \r\n\
+              type=\'application/atom+xml;type=feed\' \r\n\
+              me:attr1=\'a1\' \r\n\
+              attr2=\'a2\'/> \r\n";
+
+        var link = OData.atomReadLink(datajs.xmlParse(linkString).documentElement);
+
+        djstest.assert(link, "atomReadLink didn't return a link object");
+        djstest.assertAreEqual(link.href, "http://nexturi", "atomReadLink, link object href field has an unexpected value");
+        djstest.assertAreEqual(link.rel, "next", "atomReadLink, link object rel field has an unexpected value");
+        djstest.assertAreEqual(link.type, "application/atom+xml;type=feed", "atomReadLink, link object type field has an unexpected value");
+        djstest.assertAreEqual(link.extensions.length, 2, "atomReadLink, link object extensions doesn't have the expected number of extensions");
+
+        djstest.done();
+
+    });
+
+    djstest.addTest(function atomReadLinkThrowHrefMissingTest() {
+        var linkString = "\
+        <link xmlns:me=\'http://myExtensions\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\' \r\n\
+              rel=\'next\' \r\n\
+              type=\'application/atom+xml;type=feed\' \r\n\
+              me:attr1=\'a1\' \r\n\
+              attr2=\'a2\'/> \r\n";
+
+
+        var linkRoot = datajs.xmlParse(linkString).documentElement;
+        djstest.expectException(function () {
+            OData.atomReadLink(linkRoot);
+        }, "atomReadLink didn't throw an exception when the link doesn't have the href attribute");
+        djstest.done();
+
+    });
+
+    djstest.addTest(function atomReadExtensionElementTest() {
+        var extensionString = "\
+        <me:ext xmlns:me=\'http://myExtensions\' me:attr1=\'a1\' attr2=\'a2\'> \r\n\
+          <ext>e1</ext> \r\n\
+        </me:ext> \r\n";
+
+        var validateExtension = function (ext, name, namespaceURI, attributeCount, childrenCount, value) {
+            djstest.assertAreEqual(ext.name, name, "atomReadExtensionElement, extension object name field has an unexpected value");
+            djstest.assertAreEqual(ext.namespaceURI, namespaceURI, "atomReadExtensionElement, extension object namespaceURI field has an unexpected value");
+            djstest.assertAreEqual(ext.attributes.length, attributeCount, "atomReadExtensionElement, extension object attributes doesn't have the expected number of attributes");
+            djstest.assertAreEqual(ext.children.length, childrenCount, "atomReadExtensionElement, extension object attributes doesn't have the expected number of children");
+            djstest.assertAreEqual(ext.value, value, "atomReadExtensionElement, extension object value field has an unexpected value");
+        };
+
+        var extension = OData.atomReadExtensionElement(datajs.xmlParse(extensionString).documentElement);
+        validateExtension(extension, "ext", "http://myExtensions", 2, 1);
+
+        extension = extension.children[0];
+        validateExtension(extension, "ext", null, 0, 0, "e1");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadExtensionAttributesTest() {
+        var extensionString = "\
+        <me:ext xmlns:me=\'http://myExtensions\' me:attr1=\'a1\' attr2=\'a2\' /> \r\n";
+
+        var extensionAttributes = OData.atomReadExtensionAttributes(datajs.xmlParse(extensionString).documentElement);
+        djstest.assertAreEqual(extensionAttributes.length, 2, "atomReadExtensionAttribute, returned collection doesn't have the expected number of attributes");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadExtensionAttributeTest() {
+
+        var tests = {
+            "extension with namespace": {
+                i: '<me:ext xmlns:me="http://myExtensions" me:attr1="a1" />',
+                e: { name: "attr1", namespaceURI: "http://myExtensions", value: "a1" }
+            },
+            "extension without namespace": {
+                i: '<me:ext xmlns:me="http://myExtensions" attr2="a2" />',
+                e: { name: "attr2", namespaceURI: null, value: "a2" }
+            }
+        };
+
+        for (var name in tests) {
+            var test = tests[name];
+            var xmlElement = datajs.xmlParse(test.i).documentElement;
+            var extensions = OData.atomReadExtensionAttributes(xmlElement);
+
+            djstest.assertAreEqualDeep(extensions[0], test.e, name + " - extension object is the expected one");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryTest() {
+        var entryString = "\
+        <entry xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+               xmlns:d2=\'http://schemas.microsoft.com/ado/2007/08/dataservices\' \r\n\
+               xmlns:m2=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+               xmlns:atom=\'http://www.w3.org/2005/Atom\' \r\n\
+               xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+               xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>the id</id> \r\n\
+           <category term=\'the type\' \r\n\
+                     scheme=\'http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\' /> \r\n\
+           <content type=\'application/xml\'> \r\n\
+            <m2:properties xmlns=\'http://schemas.microsoft.com/ado/2007/08/dataservices\'>\r\n\
+             <Untyped>untyped value</Untyped> \r\n\
+             <Typed m2:type='Edm.Int32'>100</Typed> \r\n\
+            </m2:properties> \r\n\
+           </content> \r\n\
+           <link rel=\'self\' href=\'http://selfuri\' /> \r\n\
+        </entry>\r\n";
+
+        var expectedEntry = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/the id",
+                uri_extensions: [],
+                type: "the type",
+                type_extensions: [],
+                self: "http://selfuri",
+                self_link_extensions: [],
+                properties: {
+                    Untyped: {
+                        type: "Edm.String",
+                        extensions: []
+                    },
+                    Typed: {
+                        type: "Edm.Int32",
+                        extensions: []
+                    }
+                }
+            },
+            Untyped: "untyped value",
+            Typed: 100
+        };
+
+        var entry = OData.atomReadEntry(datajs.xmlParse(entryString).documentElement);
+
+        djstest.assert(entry, "atomReadEntry didn't return an entry object");
+        djstest.assertAreEqualDeep(entry, expectedEntry);
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlCRSValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <PointQualified>                                                            \r\n\
+                   <gml:Point gml:srsName='http://www.opengis.net/def/crs/EPSG/0/1234'>      \r\n\
+                      <gml:pos>1 2 3 4</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointQualified>                                                           \r\n\
+                 <PointUnQualified>                                                          \r\n\
+                   <gml:Point srsName='http://www.opengis.net/def/crs/EPSG/0/5678'>          \r\n\
+                      <gml:pos>5 6 7 8</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointUnQualified>                                                         \r\n\
+               </m:properties>                                                               \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    PointQualified: { type: "Edm.Geometry", extensions: [] },
+                    PointUnQualified: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            PointQualified: {
+                __metadata: { type: "Edm.Geometry" },
+                crs: {
+                    type: "name",
+                    properties: {
+                        name: "EPSG:1234"
+                    }
+                },
+                type: "Point",
+                coordinates: [1, 2, 3, 4]
+            },
+            PointUnQualified: {
+                __metadata: { type: "Edm.Geometry" },
+                crs: {
+                    type: "name",
+                    properties: {
+                        name: "EPSG:5678"
+                    }
+                },
+                type: "Point",
+                coordinates: [5, 6, 7, 8]
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlUnknownCRSValueThrowsTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <Point>                                                                     \r\n\
+                   <gml:Point srsName='http://www.opengis.net/def/crs/EPSG/1/1234'>          \r\n\
+                      <gml:pos>1 2 3 4</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </Point>                                                                     \r\n\
+               </m:properties>                                                               \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        try {
+            OData.atomHandler.read(response);
+            djstest.fail("An exception was expected");
+        } catch (e) {
+            djstest.assert(e.message.indexOf("Unsupported srs name:") === 0, "Error is the expected one");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlPointValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <Point>                                                                     \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos>1 2 -3 4</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </Point>                                                                    \r\n\
+                 <PointWithExtraTags>                                                        \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:name>the point</gml:name>                                         \r\n\
+                      <gml:pos>5 6 7 8</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointWithExtraTags>                                                       \r\n\
+                 <EmptyPoint >                                                               \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos/>                                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </EmptyPoint>                                                               \r\n\
+                 <PointWithSpacesInValue>                                                    \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos>  8  9 10   11      12 </gml:pos>                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointWithSpacesInValue>                                                   \r\n\
+                 <PointWithSingleValue>                                                      \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos>13</gml:pos>                                                  \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointWithSingleValue>                                                     \r\n\
+                 <PointWithSingleValueAndSpaces>                                             \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos> 14 </gml:pos>                                                \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointWithSingleValueAndSpaces>                                            \r\n\
+              </m:properties>                                                                \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    Point: { type: "Edm.Geometry", extensions: [] },
+                    PointWithExtraTags: { type: "Edm.Geometry", extensions: [] },
+                    EmptyPoint: { type: "Edm.Geometry", extensions: [] },
+                    PointWithSpacesInValue: { type: "Edm.Geometry", extensions: [] },
+                    PointWithSingleValue: { type: "Edm.Geometry", extensions: [] },
+                    PointWithSingleValueAndSpaces: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            Point: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [1, 2, -3, 4]
+            },
+            PointWithExtraTags: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [5, 6, 7, 8]
+            },
+            EmptyPoint: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: []
+            },
+            PointWithSpacesInValue: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [8, 9, 10, 11, 12]
+            },
+            PointWithSingleValue: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [13]
+            },
+            PointWithSingleValueAndSpaces: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [14]
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlLineStringValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+                <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'  \r\n\
+                             xmlns:gml='http://www.opengis.net/gml'>                         \r\n\
+                 <LineStringExtraTags>                                                       \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:name>the line</gml:name>                                           \r\n\
+                     <gml:posList>1.0 2.0 3.0 4.0</gml:posList>                              \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringExtraTags>                                                      \r\n\
+                 <LineStringPosList>                                                         \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:posList>5.0 6.0 7.0 8.0</gml:posList>                              \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringPosList>                                                        \r\n\
+                 <LineStringEmptyPosList>                                                    \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:posList/>                                                          \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringEmptyPosList>                                                   \r\n\
+                 <LineStringPosAndPoint>                                                     \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:pos>7 8</gml:pos>                                                  \r\n\
+                     <gml:pointProperty>                                                     \r\n\
+                        <gml:Point>                                                          \r\n\
+                          <gml:pos>9 10 11 12</gml:pos>                                      \r\n\
+                        </gml:Point>                                                         \r\n\
+                     </gml:pointProperty>                                                    \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringPosAndPoint>                                                    \r\n\
+                 <LineStringEmptyPosAndPoint>                                                \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:pos/>                                                              \r\n\
+                     <gml:pointProperty>                                                     \r\n\
+                        <gml:Point>                                                          \r\n\
+                          <gml:pos/>                                                         \r\n\
+                        </gml:Point>                                                         \r\n\
+                     </gml:pointProperty>                                                    \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringEmptyPosAndPoint>                                               \r\n\
+               </m:properties>                                                               \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    LineStringExtraTags: { type: "Edm.Geometry", extensions: [] },
+                    LineStringPosList: { type: "Edm.Geometry", extensions: [] },
+                    LineStringEmptyPosList: { type: "Edm.Geometry", extensions: [] },
+                    LineStringPosAndPoint: { type: "Edm.Geometry", extensions: [] },
+                    LineStringEmptyPosAndPoint: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            LineStringExtraTags: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "LineString",
+                coordinates: [[1, 2], [3, 4]]
+            },
+            LineStringPosList: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "LineString",
+                coordinates: [[5, 6], [7, 8]]
+            },
+            LineStringEmptyPosList: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "LineString",
+                coordinates: []
+            },
+            LineStringPosAndPoint: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "LineString",
+                coordinates: [[7, 8], [9, 10, 11, 12]]
+            },
+            LineStringEmptyPosAndPoint: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "LineString",
+                coordinates: [[], []]
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlLineStringValueWithOddPosListTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+                <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'  \r\n\
+                             xmlns:gml='http://www.opengis.net/gml'>                         \r\n\
+                 <LineString>                                                                \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:posList>1.0 2.0 3.0</gml:posList>                                  \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineString>                                                               \r\n\
+               </m:properties>                                                               \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        try {
+            OData.atomHandler.read(response);
+            djstest.fail("An exception was expected");
+        } catch (e) {
+            djstest.assertAreEqual(e.message, "GML posList element has an uneven number of numeric values");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlPolygonValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+                <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'  \r\n\
+                             xmlns:gml='http://www.opengis.net/gml'>                         \r\n\
+                 <PolygonExteriorOnly>                                                       \r\n\
+                   <gml:Polygon>                                                             \r\n\
+                     <gml:exterior>                                                          \r\n\
+                       <gml:LinearRing>                                                      \r\n\
+                         <gml:pos>1.0 2.0</gml:pos>                                          \r\n\
+                       </gml:LinearRing>                                                     \r\n\
+                     </gml:exterior>                                                         \r\n\
+                   </gml:Polygon>                                                            \r\n\
+                 </PolygonExteriorOnly>                                                      \r\n\
+                 <PolygonExteriorInterior>                                                   \r\n\
+                   <gml:Polygon>                                                             \r\n\
+                     <gml:exterior>                                                          \r\n\
+                       <gml:LinearRing>                                                      \r\n\
+                         <gml:pos>3.0 4.0</gml:pos>                                          \r\n\
+                       </gml:LinearRing>                                                     \r\n\
+                     </gml:exterior>                                                         \r\n\
+                     <gml:interior>                                                          \r\n\
+                       <gml:LinearRing>                                                      \r\n\
+                         <gml:pos>5.0 6.0</gml:pos>                                          \r\n\
+                       </gml:LinearRing>                                                     \r\n\
+                     </gml:interior>                                                         \r\n\
+                   </gml:Polygon>                                                            \r\n\
+                 </PolygonExteriorInterior>                                                  \r\n\
+                 <PolygonInteriorFirst>                                                      \r\n\
+                   <gml:Polygon>                                                             \r\n\
+                     <gml:interior>                                                          \r\n\
+                       <gml:LinearRing>                                                      \r\n\
+                         <gml:pos>9.0 10.0</gml:pos>                                         \r\n\
+                       </gml:LinearRing>                                                     \r\n\
+                     </gml:interior>                                                         \r\n\
+                     <gml:exterior>                                                          \r\n\
+                       <gml:LinearRing>                                                      \r\n\
+                         <gml:pos>11.0 12.0</gml:pos>                                        \r\n\
+                       </gml:LinearRing>                                                     \r\n\
+                     </gml:exterior>                                                         \r\n\
+                   </gml:Polygon>                                                            \r\n\
+                 </PolygonInteriorFirst>                                                     \r\n\
+                 <PolygonInteriorOnly>                                                       \r\n\
+                   <gml:Polygon>                                                             \r\n\
+                     <gml:interior>                                                          \r\n\
+                       <gml:LinearRing>                                                      \r\n\
+                         <gml:pos>13.0 14.0</gml:pos>                                        \r\n\
+                       </gml:LinearRing>                                                     \r\n\
+                     </gml:interior>                                                         \r\n\
+                     <gml:interior>                                                          \r\n\
+                       <gml:LinearRing>                                                      \r\n\
+                         <gml:pos>15.0 16.0</gml:pos>                                        \r\n\
+                       </gml:LinearRing>                                                     \r\n\
+                     </gml:interior>                                                         \r\n\
+                   </gml:Polygon>                                                            \r\n\
+                 </PolygonInteriorOnly>                                                      \r\n\
+                 <EmptyPolygon>                                                              \r\n\
+                   <gml:Polygon/>                                                            \r\n\
+                 </EmptyPolygon>                                                             \r\n\
+                 <PolygonEmptyExteriorInterior>                                              \r\n\
+                   <gml:Polygon>                                                             \r\n\
+                     <gml:interior>                                                          \r\n\
+                       <gml:LinearRing>                                                      \r\n\
+                         <gml:pos/>                                                          \r\n\
+                       </gml:LinearRing>                                                     \r\n\
+                     </gml:interior>                                                         \r\n\
+                     <gml:exterior>                                                          \r\n\
+                       <gml:LinearRing>                                                      \r\n\
+                         <gml:pos/>                                                          \r\n\
+                       </gml:LinearRing>                                                     \r\n\
+                     </gml:exterior>                                                         \r\n\
+                   </gml:Polygon>                                                            \r\n\
+                 </PolygonEmptyExteriorInterior>                                             \r\n\
+               </m:properties>                                                               \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    PolygonExteriorOnly: { type: "Edm.Geometry", extensions: [] },
+                    PolygonExteriorInterior: { type: "Edm.Geometry", extensions: [] },
+                    PolygonInteriorFirst: { type: "Edm.Geometry", extensions: [] },
+                    PolygonInteriorOnly: { type: "Edm.Geometry", extensions: [] },
+                    EmptyPolygon: { type: "Edm.Geometry", extensions: [] },
+                    PolygonEmptyExteriorInterior: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            PolygonExteriorOnly: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Polygon",
+                coordinates: [[[1, 2]]]
+            },
+            PolygonExteriorInterior: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Polygon",
+                coordinates: [[[3, 4]], [[5, 6]]]
+            },
+            PolygonInteriorFirst: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Polygon",
+                coordinates: [[[11, 12]], [[9, 10]]]
+            },
+            PolygonInteriorOnly: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Polygon",
+                coordinates: [[[]], [[13, 14]], [[15, 16]]]
+            },
+            EmptyPolygon: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Polygon",
+                coordinates: []
+            },
+            PolygonEmptyExteriorInterior: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Polygon",
+                coordinates: [[[]], [[]]]
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlMultiPointValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <EmptyMultiPoint>                                                           \r\n\
+                   <gml:MultiPoint/>                                                         \r\n\
+                 </EmptyMultiPoint>                                                          \r\n\
+                 <MultiPoint>                                                                \r\n\
+                   <gml:MultiPoint>                                                          \r\n\
+                     <gml:pointMember>                                                       \r\n\
+                       <gml:Point>                                                           \r\n\
+                         <gml:pos>1 2</gml:pos>                                              \r\n\
+                       </gml:Point>                                                          \r\n\
+                     </gml:pointMember>                                                      \r\n\
+                     <gml:pointMember>                                                       \r\n\
+                       <gml:Point>                                                           \r\n\
+                         <gml:pos>3 4</gml:pos>                                              \r\n\
+                       </gml:Point>                                                          \r\n\
+                     </gml:pointMember>                                                      \r\n\
+                     <gml:pointMembers>                                                      \r\n\
+                       <gml:Point>                                                           \r\n\
+                         <gml:pos>5 6</gml:pos>                                              \r\n\
+                       </gml:Point>                                                          \r\n\
+                       <gml:Point>                                                           \r\n\
+                         <gml:pos>7 8</gml:pos>                                              \r\n\
+                       </gml:Point>                                                          \r\n\
+                     </gml:pointMembers>                                                     \r\n\
+                   </gml:MultiPoint>                                                         \r\n\
+                 </MultiPoint>                                                               \r\n\
+                 <MultiPointEmptyMembers>                                                    \r\n\
+                   <gml:MultiPoint>                                                          \r\n\
+                     <gml:pointMember/>                                                      \r\n\
+                     <gml:pointMembers/>                                                     \r\n\
+                   </gml:MultiPoint>                                                         \r\n\
+                 </MultiPointEmptyMembers>                                                   \r\n\
+              </m:properties>                                                                \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    EmptyMultiPoint: { type: "Edm.Geometry", extensions: [] },
+                    MultiPoint: { type: "Edm.Geometry", extensions: [] },
+                    MultiPointEmptyMembers: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            EmptyMultiPoint: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "MultiPoint",
+                coordinates: []
+            },
+            MultiPoint: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "MultiPoint",
+                coordinates: [[1, 2], [3, 4], [5, 6], [7, 8]]
+            },
+            MultiPointEmptyMembers: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "MultiPoint",
+                coordinates: []
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlMultiListStringValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <EmptyMultiLineString>                                                      \r\n\
+                   <gml:MultiCurve/>                                                         \r\n\
+                 </EmptyMultiLineString>                                                     \r\n\
+                 <MultiLineString>                                                           \r\n\
+                   <gml:MultiCurve>                                                          \r\n\
+                     <gml:name>The multi line string</gml:name>                              \r\n\
+                     <gml:curveMember>                                                       \r\n\
+                       <gml:LineString>                                                      \r\n\
+                         <gml:posList>1 2 3 4</gml:posList>                                  \r\n\
+                       </gml:LineString>                                                     \r\n\
+                     </gml:curveMember>                                                      \r\n\
+                     <gml:curveMember>                                                       \r\n\
+                       <gml:LineString>                                                      \r\n\
+                         <gml:posList>5 6 7 8</gml:posList>                                  \r\n\
+                       </gml:LineString>                                                     \r\n\
+                     </gml:curveMember>                                                      \r\n\
+                     <gml:curveMembers>                                                      \r\n\
+                       <gml:LineString>                                                      \r\n\
+                         <gml:posList>9 10 11 12</gml:posList>                               \r\n\
+                       </gml:LineString>                                                     \r\n\
+                       <gml:LineString>                                                      \r\n\
+                         <gml:posList>13 14 15 16</gml:posList>                              \r\n\
+                       </gml:LineString>                                                     \r\n\
+                     </gml:curveMembers>                                                     \r\n\
+                   </gml:MultiCurve>                                                         \r\n\
+                 </MultiLineString>                                                          \r\n\
+                 <MultiLineStringEmptyMembers>                                               \r\n\
+                   <gml:MultiCurve>                                                          \r\n\
+                     <gml:curveMember/>                                                      \r\n\
+                     <gml:curveMembers/>                                                     \r\n\
+                   </gml:MultiCurve>                                                         \r\n\
+                 </MultiLineStringEmptyMembers>                                              \r\n\
+              </m:properties>                                                                \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    EmptyMultiLineString: { type: "Edm.Geometry", extensions: [] },
+                    MultiLineString: { type: "Edm.Geometry", extensions: [] },
+                    MultiLineStringEmptyMembers: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            EmptyMultiLineString: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "MultiLineString",
+                coordinates: []
+            },
+            MultiLineString: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "MultiLineString",
+                coordinates: [[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]], [[13, 14], [15, 16]]]
+            },
+            MultiLineStringEmptyMembers: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "MultiLineString",
+                coordinates: []
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlMultiPolygonValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <EmptyMultiPolygon>                                                         \r\n\
+                   <gml:MultiSurface/>                                                       \r\n\
+                 </EmptyMultiPolygon>                                                        \r\n\
+                 <MultiPolygon>                                                              \r\n\
+                   <gml:MultiSurface>                                                        \r\n\
+                     <gml:name>The multi surface</gml:name>                                  \r\n\
+                     <gml:surfaceMember>                                                     \r\n\
+                       <gml:Polygon>                                                         \r\n\
+                         <gml:exterior>                                                      \r\n\
+                           <gml:LinearRing>                                                  \r\n\
+                             <gml:pos>1 2</gml:pos>                                          \r\n\
+                           </gml:LinearRing>                                                 \r\n\
+                         </gml:exterior>                                                     \r\n\
+                         <gml:interior>                                                      \r\n\
+                           <gml:LinearRing>                                                  \r\n\
+                             <gml:pos>3 4</gml:pos>                                          \r\n\
+                           </gml:LinearRing>                                                 \r\n\
+                         </gml:interior>                                                     \r\n\
+                        </gml:Polygon>                                                       \r\n\
+                     </gml:surfaceMember>                                                    \r\n\
+                     <gml:surfaceMember>                                                     \r\n\
+                       <gml:Polygon>                                                         \r\n\
+                         <gml:exterior>                                                      \r\n\
+                           <gml:LinearRing>                                                  \r\n\
+                             <gml:pos>5 6</gml:pos>                                          \r\n\
+                           </gml:LinearRing>                                                 \r\n\
+                         </gml:exterior>                                                     \r\n\
+                         <gml:interior>                                                      \r\n\
+                           <gml:LinearRing>                                                  \r\n\
+                             <gml:pos>7 8</gml:pos>                                          \r\n\
+                           </gml:LinearRing>                                                 \r\n\
+                         </gml:interior>                                                     \r\n\
+                       </gml:Polygon>                                                        \r\n\
+                     </gml:surfaceMember>                                                    \r\n\
+                     <gml:surfaceMembers>                                                    \r\n\
+                       <gml:Polygon>                                                         \r\n\
+                         <gml:exterior>                                                      \r\n\
+                           <gml:LinearRing>                                                  \r\n\
+                             <gml:pos>9 10</gml:pos>                                         \r\n\
+                           </gml:LinearRing>                                                 \r\n\
+                         </gml:exterior>                                                     \r\n\
+                         <gml:interior>                                                      \r\n\
+                           <gml:LinearRing>                                                  \r\n\
+                             <gml:pos>11 12</gml:pos>                                        \r\n\
+                           </gml:LinearRing>                                                 \r\n\
+                         </gml:interior>                                                     \r\n\
+                       </gml:Polygon>                                                        \r\n\
+                       <gml:Polygon>                                                         \r\n\
+                         <gml:exterior>                                                      \r\n\
+                           <gml:LinearRing>                                                  \r\n\
+                             <gml:pos>13 14</gml:pos>                                        \r\n\
+                           </gml:LinearRing>                                                 \r\n\
+                         </gml:exterior>                                                     \r\n\
+                         <gml:interior>                                                      \r\n\
+                           <gml:LinearRing>                                                  \r\n\
+                             <gml:pos>15 16</gml:pos>                                        \r\n\
+                           </gml:LinearRing>                                                 \r\n\
+                         </gml:interior>                                                     \r\n\
+                       </gml:Polygon>                                                        \r\n\
+                     </gml:surfaceMembers>                                                   \r\n\
+                   </gml:MultiSurface>                                                       \r\n\
+                 </MultiPolygon>                                                             \r\n\
+                 <MultiPolygonEmptyMembers>                                                  \r\n\
+                   <gml:MultiSurface>                                                        \r\n\
+                     <gml:surfaceMember/>                                                    \r\n\
+                     <gml:surfaceMembers/>                                                   \r\n\
+                   </gml:MultiSurface>                                                       \r\n\
+                 </MultiPolygonEmptyMembers>                                                 \r\n\
+              </m:properties>                                                                \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    EmptyMultiPolygon: { type: "Edm.Geometry", extensions: [] },
+                    MultiPolygon: { type: "Edm.Geometry", extensions: [] },
+                    MultiPolygonEmptyMembers: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            EmptyMultiPolygon: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "MultiPolygon",
+                coordinates: []
+            },
+            MultiPolygon: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "MultiPolygon",
+                coordinates: [
+                  [[[1, 2]], [[3, 4]]], [[[5, 6]], [[7, 8]]], [[[9, 10]], [[11, 12]]], [[[13, 14]], [[15, 16]]]
+                ]
+            },
+            MultiPolygonEmptyMembers: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "MultiPolygon",
+                coordinates: []
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlMultiGeometryValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <EmptyMultiGeometry>                                                        \r\n\
+                   <gml:MultiGeometry/>                                                      \r\n\
+                 </EmptyMultiGeometry>                                                       \r\n\
+                 <MultiGeometry>                                                             \r\n\
+                   <gml:MultiGeometry>                                                       \r\n\
+                     <gml:geometryMember>                                                    \r\n\
+                       <gml:Point>                                                           \r\n\
+                         <gml:pos>1 2</gml:pos>                                              \r\n\
+                       </gml:Point>                                                          \r\n\
+                     </gml:geometryMember>                                                   \r\n\
+                     <gml:geometryMember>                                                    \r\n\
+                       <gml:Point>                                                           \r\n\
+                         <gml:pos>3 4</gml:pos>                                              \r\n\
+                       </gml:Point>                                                          \r\n\
+                     </gml:geometryMember>                                                   \r\n\
+                     <gml:geometryMembers>                                                   \r\n\
+                       <gml:LineString>                                                      \r\n\
+                         <gml:posList>5 6 7 8</gml:posList>                                  \r\n\
+                       </gml:LineString>                                                     \r\n\
+                       <gml:Polygon>                                                         \r\n\
+                         <gml:exterior>                                                      \r\n\
+                           <gml:LinearRing>                                                  \r\n\
+                             <gml:pos>9 10</gml:pos>                                         \r\n\
+                           </gml:LinearRing>                                                 \r\n\
+                         </gml:exterior>                                                     \r\n\
+                       </gml:Polygon>                                                        \r\n\
+                       <gml:MultiPoint>                                                      \r\n\
+                         <gml:pointMember>                                                   \r\n\
+                           <gml:Point>                                                       \r\n\
+                             <gml:pos>11 12</gml:pos>                                        \r\n\
+                           </gml:Point>                                                      \r\n\
+                         </gml:pointMember>                                                  \r\n\
+                       </gml:MultiPoint>                                                     \r\n\
+                       <gml:MultiCurve>                                                      \r\n\
+                         <gml:curveMember>                                                   \r\n\
+                           <gml:LineString>                                                  \r\n\
+                             <gml:posList>13 14 15 16</gml:posList>                          \r\n\
+                           </gml:LineString>                                                 \r\n\
+                         </gml:curveMember>                                                  \r\n\
+                       </gml:MultiCurve>                                                     \r\n\
+                       <gml:MultiSurface>                                                    \r\n\
+                         <gml:surfaceMember>                                                 \r\n\
+                           <gml:Polygon>                                                     \r\n\
+                             <gml:exterior>                                                  \r\n\
+                               <gml:LinearRing>                                              \r\n\
+                                 <gml:pos>17 18</gml:pos>                                    \r\n\
+                               </gml:LinearRing>                                             \r\n\
+                             </gml:exterior>                                                 \r\n\
+                             <gml:interior>                                                  \r\n\
+                               <gml:LinearRing>                                              \r\n\
+                                 <gml:pos>19 20</gml:pos>                                    \r\n\
+                               </gml:LinearRing>                                             \r\n\
+                             </gml:interior>                                                 \r\n\
+                           </gml:Polygon>                                                    \r\n\
+                         </gml:surfaceMember>                                                \r\n\
+                       </gml:MultiSurface>                                                   \r\n\
+                     </gml:geometryMembers>                                                  \r\n\
+                   </gml:MultiGeometry>                                                      \r\n\
+                 </MultiGeometry>                                                            \r\n\
+                 <MultiGeometryEmptyMembers>                                                 \r\n\
+                   <gml:MultiGeometry>                                                       \r\n\
+                     <gml:geometryMember/>                                                   \r\n\
+                     <gml:geometryMembers/>                                                  \r\n\
+                   </gml:MultiGeometry>                                                      \r\n\
+                 </MultiGeometryEmptyMembers>                                                \r\n\
+                 <NestedMultiGeometry>                                                       \r\n\
+                   <gml:MultiGeometry>                                                       \r\n\
+                     <gml:geometryMember>                                                    \r\n\
+                        <gml:MultiGeometry>                                                  \r\n\
+                           <gml:geometryMember>                                              \r\n\
+                             <gml:Point>                                                     \r\n\
+                               <gml:pos>21 22</gml:pos>                                      \r\n\
+                             </gml:Point>                                                    \r\n\
+                           </gml:geometryMember>                                             \r\n\
+                        </gml:MultiGeometry>                                                 \r\n\
+                     </gml:geometryMember>                                                   \r\n\
+                     <gml:geometryMembers>                                                   \r\n\
+                        <gml:MultiGeometry>                                                  \r\n\
+                           <gml:geometryMember>                                              \r\n\
+                             <gml:MultiGeometry>                                             \r\n\
+                               <gml:geometryMember>                                          \r\n\
+                                 <gml:Point>                                                 \r\n\
+                                   <gml:pos>23 24</gml:pos>                                  \r\n\
+                                 </gml:Point>                                                \r\n\
+                               </gml:geometryMember>                                         \r\n\
+                             </gml:MultiGeometry>                                            \r\n\
+                           </gml:geometryMember>                                             \r\n\
+                        </gml:MultiGeometry>                                                 \r\n\
+                     </gml:geometryMembers>                                                  \r\n\
+                   </gml:MultiGeometry>                                                      \r\n\
+                 </NestedMultiGeometry>                                                      \r\n\
+              </m:properties>                                                                \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    EmptyMultiGeometry: { type: "Edm.Geometry", extensions: [] },
+                    MultiGeometry: { type: "Edm.Geometry", extensions: [] },
+                    MultiGeometryEmptyMembers: { type: "Edm.Geometry", extensions: [] },
+                    NestedMultiGeometry: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            EmptyMultiGeometry: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "GeometryCollection",
+                geometries: []
+            },
+            MultiGeometry: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "GeometryCollection",
+                geometries: [
+                    {
+                        type: "Point",
+                        coordinates: [1, 2]
+                    },
+                    {
+                        type: "Point",
+                        coordinates: [3, 4]
+                    },
+                    {
+                        type: "LineString",
+                        coordinates: [[5, 6], [7, 8]]
+                    },
+                    {
+                        type: "Polygon",
+                        coordinates: [[[9, 10]]]
+                    },
+                    {
+                        type: "MultiPoint",
+                        coordinates: [[11, 12]]
+                    },
+                    {
+                        type: "MultiLineString",
+                        coordinates: [[[13, 14], [15, 16]]]
+                    },
+                    {
+                        type: "MultiPolygon",
+                        coordinates: [[[[17, 18]], [[19, 20]]]]
+                    }
+                ]
+            },
+            MultiGeometryEmptyMembers: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "GeometryCollection",
+                geometries: []
+            },
+            NestedMultiGeometry: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "GeometryCollection",
+                geometries: [
+                    {
+                        type: "GeometryCollection",
+                        geometries: [
+                            {
+                                type: "Point",
+                                coordinates: [21, 22]
+                            }
+                        ]
+                    },
+                    {
+                        type: "GeometryCollection",
+                        geometries: [
+                            {
+                                type: "GeometryCollection",
+                                geometries: [
+                                    {
+                                        type: "Point",
+                                        coordinates: [23, 24]
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                ]
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGeometryProperties() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+                <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'  \r\n\
+                             xmlns:gml='http://www.opengis.net/gml'>                         \r\n\
+                 <Point m:type='Edm.GeometryPoint'>                                          \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos/>                                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </Point>                                                                    \r\n\
+                 <LineString m:type='Edm.GeometryLineString'>                                \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:posList/>                                                          \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineString>                                                               \r\n\
+                 <Polygon m:type='Edm.GeometryPolygon'>                                      \r\n\
+                   <gml:Polygon/>                                                            \r\n\
+                 </Polygon>                                                                  \r\n\
+                 <MultiPoint m:type='Edm.GeometryMultiPoint'>                                \r\n\
+                   <gml:MultiPoint/>                                                         \r\n\
+                 </MultiPoint>                                                               \r\n\
+                 <MultiLineString m:type='Edm.GeometryMultiLineString'>                      \r\n\
+                   <gml:MultiCurve/>                                                         \r\n\
+                 </MultiLineString>                                                          \r\n\
+                 <MultiPolygon m:type='Edm.GeometryMultiPolygon'>                            \r\n\
+                   <gml:MultiSurface/>                                                       \r\n\
+                 </MultiPolygon>                                                             \r\n\
+                 <Collection m:type='Edm.GeometryCollection'>                                \r\n\
+                   <gml:MultiGeometry/>                                                      \r\n\
+                 </Collection>                                                               \r\n\
+                 <Geometry m:type='Edm.Geometry'>                                            \r\n\
+                   <gml:Point/>                                                              \r\n\
+                 </Geometry>                                                                 \r\n\
+               </m:properties>                                                               \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    Point: { type: "Edm.GeometryPoint", extensions: [] },
+                    LineString: { type: "Edm.GeometryLineString", extensions: [] },
+                    Polygon: { type: "Edm.GeometryPolygon", extensions: [] },
+                    MultiPoint: { type: "Edm.GeometryMultiPoint", extensions: [] },
+                    MultiLineString: { type: "Edm.GeometryMultiLineString", extensions: [] },
+                    MultiPolygon: { type: "Edm.GeometryMultiPolygon", extensions: [] },
+                    Collection: { type: "Edm.GeometryCollection", extensions: [] },
+                    Geometry: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            Point: {
+                __metadata: { type: "Edm.GeometryPoint" },
+                type: "Point",
+                coordinates: []
+            },
+            LineString: {
+                __metadata: { type: "Edm.GeometryLineString" },
+                type: "LineString",
+                coordinates: []
+            },
+            Polygon: {
+                __metadata: { type: "Edm.GeometryPolygon" },
+                type: "Polygon",
+                coordinates: []
+            },
+            MultiPoint: {
+                __metadata: { type: "Edm.GeometryMultiPoint" },
+                type: "MultiPoint",
+                coordinates: []
+            },
+            MultiLineString: {
+                __metadata: { type: "Edm.GeometryMultiLineString" },
+                type: "MultiLineString",
+                coordinates: []
+            },
+            MultiPolygon: {
+                __metadata: { type: "Edm.GeometryMultiPolygon" },
+                type: "MultiPolygon",
+                coordinates: []
+            },
+            Collection: {
+                __metadata: { type: "Edm.GeometryCollection" },
+                type: "GeometryCollection",
+                geometries: []
+            },
+            Geometry: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: []
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGeographyProperties() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+                <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'  \r\n\
+                             xmlns:gml='http://www.opengis.net/gml'>                         \r\n\
+                 <Point m:type='Edm.GeographyPoint'>                                         \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos>1 2 3 4</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </Point>                                                                    \r\n\
+                 <LineString m:type='Edm.GeographyLineString'>                               \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:pos>5 6 7</gml:pos>                                                \r\n\
+                     <gml:pos>8 9 10</gml:pos>                                               \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineString>                                                               \r\n\
+                 <Polygon m:type='Edm.GeographyPolygon'>                                     \r\n\
+                   <gml:Polygon>                                                             \r\n\
+                     <gml:exterior>                                                          \r\n\
+                       <gml:LinearRing>                                                      \r\n\
+                         <gml:pos>9 10</gml:pos>                                             \r\n\
+                       </gml:LinearRing>                                                     \r\n\
+                     </gml:exterior>                                                         \r\n\
+                     <gml:interior>                                                          \r\n\
+                       <gml:LinearRing>                                                      \r\n\
+                         <gml:pos>11 12</gml:pos>                                            \r\n\
+                       </gml:LinearRing>                                                     \r\n\
+                     </gml:interior>                                                         \r\n\
+                   </gml:Polygon>                                                            \r\n\
+                 </Polygon>                                                                  \r\n\
+                 <MultiPoint m:type='Edm.GeographyMultiPoint'>                               \r\n\
+                   <gml:MultiPoint>                                                          \r\n\
+                     <gml:pointMember>                                                       \r\n\
+                       <gml:Point>                                                           \r\n\
+                          <gml:pos>13 14</gml:pos>                                           \r\n\
+                       </gml:Point>                                                          \r\n\
+                     </gml:pointMember>                                                      \r\n\
+                   </gml:MultiPoint>                                                         \r\n\
+                 </MultiPoint>                                                               \r\n\
+                 <MultiLineString m:type='Edm.GeographyMultiLineString'>                     \r\n\
+                   <gml:MultiCurve>                                                          \r\n\
+                     <gml:curveMember>                                                       \r\n\
+                       <gml:LineString>                                                      \r\n\
+                         <gml:posList>15 16 17 18</gml:posList>                              \r\n\
+                       </gml:LineString>                                                     \r\n\
+                     </gml:curveMember>                                                      \r\n\
+                   </gml:MultiCurve>                                                         \r\n\
+                 </MultiLineString>                                                          \r\n\
+                 <MultiPolygon m:type='Edm.GeographyMultiPolygon'>                           \r\n\
+                   <gml:MultiSurface>                                                        \r\n\
+                     <gml:surfaceMember>                                                     \r\n\
+                       <gml:Polygon>                                                         \r\n\
+                         <gml:exterior>                                                      \r\n\
+                           <gml:LinearRing>                                                  \r\n\
+                             <gml:pos>17 18</gml:pos>                                        \r\n\
+                           </gml:LinearRing>                                                 \r\n\
+                         </gml:exterior>                                                     \r\n\
+                         <gml:interior>                                                      \r\n\
+                           <gml:LinearRing>                                                  \r\n\
+                             <gml:pos>19 20</gml:pos>                                        \r\n\
+                           </gml:LinearRing>                                                 \r\n\
+                         </gml:interior>                                                     \r\n\
+                       </gml:Polygon>                                                        \r\n\
+                     </gml:surfaceMember>                                                    \r\n\
+                   </gml:MultiSurface>                                                       \r\n\
+                 </MultiPolygon>                                                             \r\n\
+                 <Collection m:type='Edm.GeographyCollection'>                               \r\n\
+                   <gml:MultiGeometry>                                                       \r\n\
+                     <gml:geometryMember>                                                    \r\n\
+                        <gml:MultiGeometry>                                                  \r\n\
+                           <gml:geometryMember>                                              \r\n\
+                             <gml:Point>                                                     \r\n\
+                               <gml:pos>21 22</gml:pos>                                      \r\n\
+                             </gml:Point>                                                    \r\n\
+                           </gml:geometryMember>                                             \r\n\
+                        </gml:MultiGeometry>                                                 \r\n\
+                     </gml:geometryMember>                                                   \r\n\
+                     <gml:geometryMembers>                                                   \r\n\
+                        <gml:MultiGeometry>                                                  \r\n\
+                           <gml:geometryMember>                                              \r\n\
+                             <gml:MultiGeometry>                                             \r\n\
+                               <gml:geometryMember>                                          \r\n\
+                                 <gml:Point>                                                 \r\n\
+                                   <gml:pos>23 24</gml:pos>                                  \r\n\
+                                 </gml:Point>                                                \r\n\
+                               </gml:geometryMember>                                         \r\n\
+                             </gml:MultiGeometry>                                            \r\n\
+                           </gml:geometryMember>                                             \r\n\
+                        </gml:MultiGeometry>                                                 \r\n\
+                     </gml:geometryMembers>                                                  \r\n\
+                   </gml:MultiGeometry>                                                      \r\n\
+                 </Collection>                                                               \r\n\
+                 <Geography m:type='Edm.Geography'>                                          \r\n\
+                   <gml:Point>                                                               \r\n\
+                     <gml:pos>25 26</gml:pos>                                                \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </Geography>                                                                \r\n\
+               </m:properties>                                                               \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    Point: { type: "Edm.GeographyPoint", extensions: [] },
+                    LineString: { type: "Edm.GeographyLineString", extensions: [] },
+                    Polygon: { type: "Edm.GeographyPolygon", extensions: [] },
+                    MultiPoint: { type: "Edm.GeographyMultiPoint", extensions: [] },
+                    MultiLineString: { type: "Edm.GeographyMultiLineString", extensions: [] },
+                    MultiPolygon: { type: "Edm.GeographyMultiPolygon", extensions: [] },
+                    Collection: { type: "Edm.GeographyCollection", extensions: [] },
+                    Geography: { type: "Edm.Geography", extensions: [] }
+                }
+            },
+            Point: {
+                __metadata: { type: "Edm.GeographyPoint" },
+                type: "Point",
+                coordinates: [2, 1, 3, 4]
+            },
+            LineString: {
+                __metadata: { type: "Edm.GeographyLineString" },
+                type: "LineString",
+                coordinates: [[6, 5, 7], [9, 8, 10]]
+            },
+            Polygon: {
+                __metadata: { type: "Edm.GeographyPolygon" },
+                type: "Polygon",
+                coordinates: [[[10, 9]], [[12, 11]]]
+            },
+            MultiPoint: {
+                __metadata: { type: "Edm.GeographyMultiPoint" },
+                type: "MultiPoint",
+                coordinates: [[14, 13]]
+            },
+            MultiLineString: {
+                __metadata: { type: "Edm.GeographyMultiLineString" },
+                type: "MultiLineString",
+                coordinates: [[[16, 15], [18, 17]]]
+            },
+            MultiPolygon: {
+                __metadata: { type: "Edm.GeographyMultiPolygon" },
+                type: "MultiPolygon",
+                coordinates: [[[[18, 17]], [[20, 19]]]]
+            },
+            Collection: {
+                __metadata: { type: "Edm.GeographyCollection" },
+                type: "GeometryCollection",
+                geometries: [
+                    {
+                        type: "GeometryCollection",
+                        geometries: [
+                            {
+                                type: "Point",
+                                coordinates: [22, 21]
+                            }
+                        ]
+                    },
+                    {
+                        type: "GeometryCollection",
+                        geometries: [
+                            {
+                                type: "GeometryCollection",
+                                geometries: [
+                                    {
+                                        type: "Point",
+                                        coordinates: [24, 23]
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                ]
+            },
+            Geography: {
+                __metadata: { type: "Edm.Geography" },
+                type: "Point",
+                coordinates: [26, 25]
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryMediaLinkEntryTest() {
+        var entryString = "\
+        <entry xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+               xmlns:m=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+               xmlns:atom=\'http://www.w3.org/2005/Atom\' \r\n\
+               xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+               xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>the id</id> \r\n\
+           <link m:etag=\'etag\' rel=\'edit-media\' href=\'http://editmediauri\' /> \r\n\
+           <content type=\'application/png\' src=\'mediaSource/source.png' /> \r\n\
+           <m:properties xmlns=\'http://schemas.microsoft.com/ado/2007/08/dataservices\'>\r\n\
+             <Untyped>untyped value</Untyped> \r\n\
+             <Typed m:type='Edm.Int32'>100</Typed> \r\n\
+           </m:properties> \r\n\
+        </entry>\r\n";
+
+        var expectedEntry = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/the id",
+                uri_extensions: [],
+                media_src: "http://services.odata.org/OData/OData.svc/mediaSource/source.png",
+                content_type: "application/png",
+                edit_media: "http://editmediauri",
+                edit_media_extensions: [],
+                media_etag: "etag",
+                properties: {
+                    Untyped: {
+                        type: "Edm.String",
+                        extensions: []
+                    },
+                    Typed: {
+                        type: "Edm.Int32",
+                        extensions: []
+                    }
+                }
+            },
+            Untyped: "untyped value",
+            Typed: 100
+        };
+
+        var entry = OData.atomReadEntry(datajs.xmlParse(entryString).documentElement);
+
+        djstest.assert(entry, "atomReadFeed didn't return an entry object for the media link entry payload");
+        djstest.assertAreEqualDeep(entry, expectedEntry);
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryTypeTest() {
+        var categoryString = "\
+        <category term=\'the type\' \r\n\
+                  scheme=\'http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\' \r\n\
+                  attr1=\'a1\' me:attr2=\'a2\' \r\n\
+                  xmlns:me=\'http//:myExtensions\' \r\n\
+                  xmlns=\'http://www.w3.org/2005/Atom\'/> \r\n";
+        var entryMetadata = {};
+        OData.atomReadEntryType(datajs.xmlParse(categoryString).documentElement, entryMetadata);
+
+        djstest.assertAreEqual(entryMetadata.type, "the type", "atomReadEntryType, entry type has an unexpected value");
+        djstest.assertAreEqual(entryMetadata.type_extensions.length, 2, "readATomEntryType, entry type_extensions doens't have the expected number of extensions");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryTypeIgnoresCategoryTest() {
+        var categoryString = "\
+        <category term=\'the type\' \r\n\
+                  attr1=\'a1\' me:attr2=\'a2\' \r\n\
+                  xmlns:me=\'http//:myExtensions\' \r\n\
+                  xmlns=\'http://www.w3.org/2005/Atom\'/> \r\n";
+
+        var entry = { __metadata: {} };
+        OData.atomReadEntryType(datajs.xmlParse(categoryString).documentElement, entry, entry.__metadata);
+
+        djstest.assert(!entry.__metadata.type, "atomReadEntryType, processed a category of without a scheme attribute!!");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryTypeThrowsWithMultipleCategoryTest() {
+        var categoryString = "\
+         <entry> \r\n\
+            <category term=\'the type\' \r\n\
+                      scheme=\'http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\' \r\n\
+                      xmlns=\'http://www.w3.org/2005/Atom\'/> \r\n\
+            <category term=\'the type\' \r\n\
+                      scheme=\'http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\' \r\n\
+                      xmlns=\'http://www.w3.org/2005/Atom\'/> \r\n\
+         </entry> \r\n";
+
+        var entry = { __metadata: {} };
+
+        djstest.expectException(function () {
+            var categories = datajs.xmlParse(categoryString).documentElement;
+            datajs.xmlChildElements(categories, function (child) {
+                OData.atomReadEntryType(child, entry, entry.__metadata);
+            });
+        }, "atomReadEntryType didn't throw the expected exception");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryContentThrowsWithNoTypeAttributeTest() {
+        var contentString = "\
+            <content src=\'http://mediasource\' xmlns=\'http://www.w3.org/2005/Atom\'/> \r\n"
+
+        var entry = { __metadata: {} };
+        var content = datajs.xmlParse(contentString).documentElement;
+        djstest.expectException(function () {
+            OData.atomReadEntryContent(content, entry);
+        }, "atomReadEntryContent didn't throw the expected exception");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryContentThrowsWithSrcAndChildrenTest() {
+        var contentString = "\
+            <content type=\'applicaton/xml\' src=\'http://mediasource\' \r\n\
+                     xmlns=\'http://www.w3.org/2005/Atom\' \r\n\
+                     xmlns:m2=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\'> \r\n\
+              <m2:properties xmlns=\'http://schemas.microsoft.com/ado/2007/08/dataservices\'>\r\n\
+                <Untyped>untyped value</Untyped> \r\n\
+                <Typed m2:type='Edm.Int32'>100</Typed> \r\n\
+              </m2:properties> \r\n\
+           </content> \r\n";
+
+        var entry = { __metadata: {} };
+        var content = datajs.xmlParse(contentString).documentElement;
+        djstest.expectException(function () {
+            OData.atomReadEntryContent(content, entry);
+        }, "atomReadEntryContent didn't throw the expected exception");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryEditMediaLinkTest() {
+        var linkString = "\
+        <link m:etag=\'etag\' rel=\'edit-media\' href=\'http://editmediauri\' \r\n\
+              attr1=\'a1\' me:attr2=\'a2\' \r\n\
+              xmlns:me=\'http://myExtensions\' \r\n\
+              xmlns:m=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'/> \r\n";
+
+        var entry = { __metadata: {} };
+
+        OData.atomReadEntryLink(datajs.xmlParse(linkString).documentElement, entry, entry.__metadata);
+
+        djstest.assertAreEqual(entry.__metadata.edit_media, "http://editmediauri", "edit_media field has a un expected value");
+        djstest.assertAreEqual(entry.__metadata.media_etag, "etag", "media_etag field has a un expected value");
+        djstest.assertAreEqual(entry.__metadata.edit_media_extensions.length, 2, "edit_media_extensions doesn't have the expected number of extensions");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryLink() {
+        var linksString = "\
+        <entry xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+         <link rel=\'edit-media\' href=\'http://editmediauri\' />\r\n\
+         <link rel=\'edit\' href=\'http://edituri\' />\r\n\
+         <link rel=\'self\' href=\'http://selfuri\' />\r\n\
+         <link rel=\'http://schemas.microsoft.com/ado/2007/08/dataservices/related/otherEntry\' \r\n\
+               type=\'application/atom+xml;type=entry\' \r\n\
+               href=\'http://otherEntryuri\' /> \r\n\
+        </entry> \r\n";
+
+        var expectedEntry = {
+            __metadata: {
+                edit: "http://edituri",
+                edit_link_extensions: [],
+                edit_media: "http://editmediauri",
+                edit_media_extensions: [],
+                self: "http://selfuri",
+                self_link_extensions: [],
+                properties: {
+                    otherEntry: {
+                        extensions: []
+                    }
+                }
+            },
+            otherEntry: {
+                __deferred: { uri: "http://otherEntryuri" }
+            }
+        };
+
+        var entry = { __metadata: {} };
+        var links = datajs.xmlParse(linksString).documentElement;
+        datajs.xmlChildElements(links, function (child) {
+            OData.atomReadEntryLink(child, entry, entry.__metadata);
+        });
+
+        djstest.assertAreEqualDeep(entry, expectedEntry);
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryNavigationPropertyTest() {
+        var entryString = "\
+        <entry xml:base='http://baseuri.org/' \r\n\
+               xmlns='http://www.w3.org/2005/Atom' \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' \r\n\
+               xmlns:me='http://myextensions'> \r\n\
+          <link rel='http://schemas.microsoft.com/ado/2007/08/dataservices/related/deferred' \r\n\
+                type='application/atom+xml;type=entry' \r\n\
+                href='entry/deferred' /> \r\n\
+          <link rel='http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks/deferred' \r\n\
+                type='application/xml' \r\n\
+                href='entry/deferred/$links' \r\n\
+                me:ext1='value1' /> \r\n\
+          <link rel='http://schemas.microsoft.com/ado/2007/08/dataservices/related/inline' \r\n\
+                type='application/atom+xml;type=feed' \r\n\
+                href='http://inline' > \r\n\
+            <m:inline> \r\n\
+                <feed xml:base='http://services.odata.org/OData/OData.svc/' \r\n\
+                      xmlns:app='http://www.w3.org/2007/app' \r\n\
+                      xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' \r\n\
+                      xmlns='http://www.w3.org/2005/Atom'> \r\n\
+                  <id>feed id</id> \r\n\
+                  <title>test feed</title> \r\n\
+                </feed> \r\n\
+           </m:inline> \r\n\
+         </link> \r\n\
+          <content type='application/xml'> \r\n\
+           <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'>\r\n\
+               <Typed m:type='Edm.Int32'>100</Typed> \r\n\
+           </m:properties> \r\n\
+         </content> \r\n\
+       </entry> \r\n";
+
+        var expectedEntry = {
+            __metadata: {
+                properties: {
+                    deferred: {
+                        associationuri: "http://baseuri.org/entry/deferred/$links",
+                        associationuri_extensions: [{ name: "ext1", namespaceURI: "http://myextensions", value: "value1"}],
+                        extensions: []
+                    },
+                    inline: {
+                        extensions: []
+                    },
+                    Typed: {
+                        type: "Edm.Int32",
+                        extensions: []
+                    }
+                }
+            },
+            deferred: {
+                __deferred: { uri: "http://baseuri.org/entry/deferred" }
+            },
+            inline: {
+                __metadata: {
+                    uri: "http://services.odata.org/OData/OData.svc/feed id",
+                    uri_extensions: [],
+                    title: "test feed",
+                    title_extensions: [],
+                    feed_extensions: []
+                },
+                results: []
+            },
+            Typed: 100
+        };
+
+        var entry = OData.atomReadEntry(datajs.xmlParse(entryString).documentElement);
+
+        djstest.assertAreEqualDeep(entry, expectedEntry);
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryStructuralObjectTest() {
+        var content = "\
+           <d:Data m:type = \"Data\" \r\n \
+                      xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" \r\n \
+                      xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\">\r\n \
+               <d:Complex m:type=\"Complex\">\r\n \
+                   <d:Property1>value 1</d:Property1>\r\n \
+                   <d:Property2>value 2</d:Property2> \r\n \
+                   <d:Nested m:type=\"Complex.Nested\">\r\n \
+                       <d:NestedProperty1>value 3</d:NestedProperty1> \r\n \
+                       <d:NestedProperty2>value 4</d:NestedProperty2> \r\n \
+                       <d:NestedProperty3>value 5</d:NestedProperty3> \r\n \
+                   </d:Nested>\r\n \
+               </d:Complex>\r\n \
+           </d:Data>\r\n";
+
+        var dataElement = datajs.xmlParse(content).documentElement;
+        var data = {};
+        var metadata = {};
+
+        OData.atomReadEntryStructuralObject(dataElement, data, metadata);
+
+        var expectedData = {
+            Complex: {
+                __metadata: { type: "Complex" },
+                Property1: "value 1",
+                Property2: "value 2",
+                Nested: {
+                    __metadata: { type: "Complex.Nested" },
+                    NestedProperty1: "value 3",
+                    NestedProperty2: "value 4",
+                    NestedProperty3: "value 5"
+                }
+            }
+        };
+
+        var expectedMetadata = {
+            properties: {
+                Complex: {
+                    type: "Complex",
+                    extensions: [],
+                    properties: {
+                        Property1: { type: "Edm.String", extensions: [] },
+                        Property2: { type: "Edm.String", extensions: [] },
+                        Nested: {
+                            type: "Complex.Nested",
+                            extensions: [],
+                            properties: {
+                                NestedProperty1: { type: "Edm.String", extensions: [] },
+                                NestedProperty2: { type: "Edm.String", extensions: [] },
+                                NestedProperty3: { type: "Edm.String", extensions: [] }
+                            }
+                        }
+                    }
+                }
+            }
+        };
+
+        djstest.assertAreEqualDeep(data, expectedData, "atomReadEntryComplexProperty didn't returned the expected data for a complex property");
+        djstest.assertAreEqualDeep(metadata, expectedMetadata, "atomReadEntryComplexProperty didn't returned the expected metadata for a complex property");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryWithComplexTypePropertiesTest() {
+        var entryString = "\
+        <entry xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+               xmlns:m2=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+               xmlns:atom=\'http://www.w3.org/2005/Atom\' \r\n\
+               xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+               xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>the id</id> \r\n\
+           <category term=\'the type\' \r\n\
+                     scheme=\'http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\' /> \r\n\
+           <content type=\'application/xml\'> \r\n\
+            <m2:properties xmlns=\'http://schemas.microsoft.com/ado/2007/08/dataservices\'>\r\n\
+              <Untyped>untyped value</Untyped> \r\n\
+              <Typed m2:type='Edm.Int32'>100</Typed> \r\n\
+              <Complex m2:type=\"Complex\">\r\n \
+                   <Property1>value 1</Property1>\r\n \
+                   <Property2>value 2</Property2> \r\n \
+                   <Nested m2:type=\"Complex.Nested\">\r\n \
+                       <NestedProperty1>value 3</NestedProperty1> \r\n \
+                       <NestedProperty2>value 4</NestedProperty2> \r\n \
+                       <NestedProperty3>value 5</NestedProperty3> \r\n \
+                   </Nested>\r\n \
+               </Complex>\r\n \
+               <UntypedComplex>\r\n\
+                   <P1>value 3</P1>\r\n\
+               </UntypedComplex>\r\n\
+           </m2:properties> \r\n\
+          </content> \r\n\
+         <link rel=\'self\' href=\'http://selfuri\' /> \r\n\
+        </entry>\r\n";
+
+        var expectedEntry = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/the id",
+                uri_extensions: [],
+                type: "the type",
+                type_extensions: [],
+                self: "http://selfuri",
+                self_link_extensions: [],
+                properties: {
+                    Untyped: {
+                        type: "Edm.String",
+                        extensions: []
+                    },
+                    Typed: {
+                        type: "Edm.Int32",
+                        extensions: []
+                    },
+                    Complex: {
+                        type: "Complex",
+                        extensions: [],
+                        properties: {
+                            Property1: { type: "Edm.String", extensions: [] },
+                            Property2: { type: "Edm.String", extensions: [] },
+                            Nested: {
+                                type: "Complex.Nested",
+                                extensions: [],
+                                properties: {
+                                    NestedProperty1: { type: "Edm.String", extensions: [] },
+                                    NestedProperty2: { type: "Edm.String", extensions: [] },
+                                    NestedProperty3: { type: "Edm.String", extensions: [] }
+                                }
+                            }
+                        }
+                    },
+                    UntypedComplex: {
+                        type: null,
+                        extensions: [],
+                        properties: {
+                            P1: { type: "Edm.String", extensions: [] }
+                        }
+                    }
+                }
+            },
+            Untyped: "untyped value",
+            Typed: 100,
+            Complex: {
+                __metadata: { type: "Complex" },
+                Property1: "value 1",
+                Property2: "value 2",
+                Nested: {
+                    __metadata: { type: "Complex.Nested" },
+                    NestedProperty1: "value 3",
+                    NestedProperty2: "value 4",
+                    NestedProperty3: "value 5"
+                }
+            },
+            UntypedComplex: {
+                __metadata: { type: null },
+                P1: "value 3"
+            }
+        };
+
+        var entry = OData.atomReadEntry(datajs.xmlParse(entryString).documentElement);
+
+        djstest.assert(entry, "atomReadEntry didn't return an entry object");
+        djstest.assertAreEqualDeep(entry, expectedEntry, "atomReadEntry didn't return the expected entry object");
+        djstest.done();
+    });
+
+
+    djstest.addTest(function atomReadEntryWithActionsAndFunctionsTest() {
+        var entryString = "\
+        <entry xml:base='http://services.odata.org/OData/OData.svc/' \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' \r\n\
+               xmlns:me='http://myExtensions' \r\n\
+               xmlns:atom='http://www.w3.org/2005/Atom' \r\n\
+               xmlns:app='http://www.w3.org/2007/app' \r\n\
+               xmlns='http://www.w3.org/2005/Atom'> \r\n\
+           <id>the id</id> \r\n\
+           <category term='the type' scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme' /> \r\n\
+           <content type='application/xml' /> \r\n\
+           <m:action metadata='#EntityContainer.Action1' title='Action1' target='http://service/entities(0)/action' /> \r\n\
+           <m:action metadata='#EntityContainer.Action2' title='Action2' target='entities(0)/action2' /> \r\n\
+           <m:action metadata='http://someService/$metadata#Container.Action1' title='Action1' target='http://someService/action' /> \r\n\
+           <m:function metadata='#EntityContainer.Function1' title='Function1' target='http://service/entities(0)/function' /> \r\n\
+           <m:function metadata='#EntityContainer.Function2' title='Function2' target='entities(0)/function2' /> \r\n\
+           <m:function metadata='http://someService/$metadata#Container.Function1' title='Function1' target='http://someService/function' /> \r\n\
+        </entry>\r\n";
+
+        var expected = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/the id",
+                uri_extensions: [],
+                type: "the type",
+                type_extensions: [],
+                actions: [
+                    {
+                        metadata: "#EntityContainer.Action1",
+                        title: "Action1",
+                        target: "http://service/entities(0)/action",
+                        extensions: []
+                    },
+                    {
+                        metadata: "#EntityContainer.Action2",
+                        title: "Action2",
+                        target: "http://services.odata.org/OData/OData.svc/entities(0)/action2",
+                        extensions: []
+                    },
+                    {
+                        metadata: "http://someService/$metadata#Container.Action1",
+                        title: "Action1",
+                        target: "http://someService/action",
+                        extensions: []
+                    }
+                ],
+                functions: [
+                    {
+                        metadata: "#EntityContainer.Function1",
+                        title: "Function1",
+                        target: "http://service/entities(0)/function",
+                        extensions: []
+                    },
+                    {
+                        metadata: "#EntityContainer.Function2",
+                        title: "Function2",
+                        target: "http://services.odata.org/OData/OData.svc/entities(0)/function2",
+                        extensions: []
+                    },
+                    {
+                        metadata: "http://someService/$metadata#Container.Function1",
+                        title: "Function1",
+                        target: "http://someService/function",
+                        extensions: []
+                    }
+                ]
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" }, body: entryString };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, expected, "atomReadEntry didn't return the expected entry object");
+        djstest.done();
+
+    });
+
+    djstest.addTest(function atomReadEntryWithNamedStreamsTest() {
+        var entryString = "\
+        <entry xml:base='http://services.odata.org/OData/OData.svc/' \r\n\
+               xmlns:m2='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' \r\n\
+               xmlns:me='http://myExtensions' \r\n\
+               xmlns:atom='http://www.w3.org/2005/Atom' \r\n\
+               xmlns:app='http://www.w3.org/2007/app' \r\n\
+               xmlns='http://www.w3.org/2005/Atom'> \r\n\
+           <id>the id</id> \r\n\
+           <category term='the type' scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme' /> \r\n\
+           <content type='application/xml' /> \r\n\
+           <link rel='http://schemas.microsoft.com/ado/2007/08/dataservices/mediaresource/readonly' href='readonly' type='image/png' me:ext1='value1' /> \r\n\
+           <link rel='http://schemas.microsoft.com/ado/2007/08/dataservices/mediaresource/readwrite' href='http://readwrite' type='image/gif' me:ext2='value2' /> \r\n\
+           <link rel='http://schemas.microsoft.com/ado/2007/08/dataservices/edit-media/readwrite' href='http://readwrite/update' type='image/gif' m2:etag='etag0' me:ext3='value3' /> \r\n\
+           <link rel='http://schemas.microsoft.com/ado/2007/08/dataservices/edit-media/writeonly' href='writeonly' type='image/jpeg' m2:etag='etag1' me:ext4='value4' /> \r\n\
+        </entry>\r\n";
+
+        var expected = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/the id",
+                uri_extensions: [],
+                type: "the type",
+                type_extensions: [],
+                properties: {
+                    readonly: {
+                        media_src_extensions: [
+                            { name: "ext1", namespaceURI: "http://myExtensions", value: "value1" }
+                        ]
+                    },
+                    readwrite: {
+                        media_src_extensions: [
+                            { name: "ext2", namespaceURI: "http://myExtensions", value: "value2" }
+                        ],
+                        edit_media_extensions: [
+                            { name: "ext3", namespaceURI: "http://myExtensions", value: "value3" }
+                        ]
+                    },
+                    writeonly: {
+                        media_src_extensions: [],
+                        edit_media_extensions: [
+                            { name: "ext4", namespaceURI: "http://myExtensions", value: "value4" }
+                        ]
+                    }
+                }
+            },
+            readonly: {
+                __mediaresource: {
+                    media_src: "http://services.odata.org/OData/OData.svc/readonly",
+                    content_type: "image/png"
+                }
+            },
+            readwrite: {
+                __mediaresource: {
+                    media_src: "http://readwrite",
+                    edit_media: "http://readwrite/update",
+                    content_type: "image/gif",
+                    media_etag: "etag0"
+                }
+            },
+            writeonly: {
+                __mediaresource: {
+                    media_src: "http://services.odata.org/OData/OData.svc/writeonly",
+                    edit_media: "http://services.odata.org/OData/OData.svc/writeonly",
+                    content_type: "image/jpeg",
+                    media_etag: "etag1"
+                }
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" }, body: entryString };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, expected, "atomReadEntry didn't return the expected entry object");
+        djstest.done();
+    });
+
+
+    djstest.addTest(function atomReadEntryWithCollectionPropertiesTest() {
+        var entryString = "\
+        <entry xml:base='http://services.odata.org/OData/OData.svc/' \r\n\
+               xmlns:m2='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+               xmlns:me=\'http://myExtensions' \r\n\
+               xmlns:atom='http://www.w3.org/2005/Atom' \r\n\
+               xmlns:app='http://www.w3.org/2007/app' \r\n\
+               xmlns='http://www.w3.org/2005/Atom'> \r\n\
+           <id>the id</id> \r\n\
+           <category term='the type' \r\n\
+                     scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme' /> \r\n\
+           <content type='application/xml'> \r\n\
+            <m2:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'>\r\n\
+              <Empty m2:type='Collection(Edm.Int32)' me:attr1='value1' /> \r\n\
+              <Primitive m2:type='Collection(Edm.Int32)'> \r\n\
+                  <element>50</element> \r\n\
+                  <element me:attr2='value2'>100</element> \r\n\
+              </Primitive>  \r\n\
+              <Complex m2:type='Collection(ns.MyType)' me:attr3='value3'> \r\n\
+                  <element me:attr4='value4'> \r\n\
+                      <Property1>value 1</Property1> \r\n\
+                      <Property2>value 2</Property2> \r\n\
+                  </element> \r\n\
+                  <element> \r\n\
+                      <Property1>value 3</Property1> \r\n\
+                      <Property2>value 4</Property2> \r\n\
+                 </element> \r\n\
+              </Complex> \r\n\
+              <SingleTyped m2:type='Collection(Edm.Int32)'> \r\n\
+                 <element>500</element> \r\n\
+              </SingleTyped> \r\n\
+              <SingleUntyped> \r\n\
+                 <element>600</element> \r\n\
+              </SingleUntyped> \r\n\
+           </m2:properties> \r\n\
+          </content> \r\n\
+         <link rel=\'self\' href=\'http://selfuri\' /> \r\n\
+        </entry>\r\n";
+
+        var expectedEntry = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/the id",
+                uri_extensions: [],
+                type: "the type",
+                type_extensions: [],
+                self: "http://selfuri",
+                self_link_extensions: [],
+                properties: {
+                    Empty: {
+                        type: "Collection(Edm.Int32)",
+                        extensions: [
+                            { name: "attr1", namespaceURI: "http://myExtensions", value: "value1" }
+                        ],
+                        elements: []
+                    },
+                    Primitive: {
+                        type: "Collection(Edm.Int32)",
+                        extensions: [],
+                        elements: [
+                            { type: "Edm.Int32", extensions: [] },
+                            {
+                                type: "Edm.Int32",
+                                extensions: [
+                                 { name: "attr2", namespaceURI: "http://myExtensions", value: "value2" }
+                              ]
+                            }
+                        ]
+                    },
+                    Complex: {
+                        type: "Collection(ns.MyType)",
+                        extensions: [
+                            { name: "attr3", namespaceURI: "http://myExtensions", value: "value3" }
+                        ],
+                        elements: [
+                            {
+                                type: "ns.MyType",
+                                extensions: [
+                                  { name: "attr4", namespaceURI: "http://myExtensions", value: "value4" }
+                                ],
+                                properties: {
+                                    Property1: { type: "Edm.String", extensions: [] },
+                                    Property2: { type: "Edm.String", extensions: [] }
+                                }
+                            },
+                            {
+                                type: "ns.MyType",
+                                extensions: [],
+                                properties: {
+                                    Property1: { type: "Edm.String", extensions: [] },
+                                    Property2: { type: "Edm.String", extensions: [] }
+                                }
+                            }
+                        ]
+                    },
+                    SingleTyped: {
+                        type: "Collection(Edm.Int32)",
+                        extensions: [],
+                        elements: [
+                            {
+                                type: "Edm.Int32",
+                                extensions: []
+                            }
+                        ]
+                    },
+                    SingleUntyped: {
+                        type: null,
+                        extensions: [],
+                        properties: {
+                            element: {
+                                type: "Edm.String",
+                                extensions: []
+                            }
+                        }
+                    }
+                }
+            },
+            Empty: {
+                __metadata: { type: "Collection(Edm.Int32)" },
+                results: []
+            },
+            Primitive: {
+                __metadata: { type: "Collection(Edm.Int32)" },
+                results: [50, 100]
+            },
+            Complex: {
+                __metadata: { type: "Collection(ns.MyType)" },
+                results: [
+                    {
+                        __metadata: { type: "ns.MyType" },
+                        Property1: "value 1",
+                        Property2: "value 2"
+                    },
+                    {
+                        __metadata: { type: "ns.MyType" },
+                        Property1: "value 3",
+                        Property2: "value 4"
+                    }
+                ]
+            },
+            SingleTyped: {
+                __metadata: { type: "Collection(Edm.Int32)" },
+                results: [500]
+            },
+            SingleUntyped: {
+                __metadata: { type: null },
+                element: "600"
+            }
+        };
+
+        // Todo refactor all this tests to use the mock http client instead. 
+        var entry = OData.atomReadEntry(datajs.xmlParse(entryString).documentElement);
+
+        djstest.assert(entry, "atomReadEntry didn't return an entry object");
+        djstest.assertAreEqualDeep(entry, expectedEntry, "atomReadEntry didn't return the expected entry object");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomSerializeFeedTest() {
+        var feed = {
+            __metadata: {
+                feed_extensions: []
+            },
+            results: [
+                { __metadata: {
+                    uri: "http://services.odata.org/OData/OData.svc/entry id",
+                    uri_extensions: [],
+                    type: "the type",
+                    type_extensions: [],
+                    properties: {
+                        Untyped: {
+                            type: "Edm.String",
+                            extensions: []
+                        },
+                        Typed: {
+                            type: "Edm.Int32",
+                            extensions: []
+                        }
+                    }
+                },
+                    Untyped: "untyped value",
+                    Typed: 100
+                }
+           ]
+        };
+
+        var feedXml = OData.atomSerializer(OData.atomHandler, feed, {});
+
+        window.ODataReadOracle.readFeedLoopback(feedXml,
+                function (expectedData) {
+                    djstest.assertAreEqualDeep(feed, expectedData, "Response data not same as expected");
+                    djstest.done();
+                });
+    });
+
+    djstest.addTest(function atomSerializeEntryTest() {
+        var entry = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/entry id",
+                uri_extensions: [],
+                type: "the type",
+                type_extensions: [],
+                properties: {
+                    Untyped: {
+                        type: "Edm.String",
+                        extensions: []
+                    },
+                    Typed: {
+                        type: "Edm.Int32",
+                        extensions: []
+                    }
+                }
+            },
+            Untyped: "untyped value",
+            Typed: 100
+        };
+
+        var entryXml = OData.atomSerializer(OData.atomHandler, entry, {});
+
+        window.ODataReadOracle.readEntryLoopback(entryXml,
+        function (expectedData) {
+            djstest.assertAreEqualDeep(entry, expectedData, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeEntryWithNamedStreamsTest() {
+        var entry = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/entry id",
+                uri_extensions: [],
+                type: "the type",
+                type_extensions: [],
+                properties: {
+                    readonly: {
+                        media_src_extensions: [
+                            { name: "ext1", namespaceURI: "http://myExtensions", value: "value1" }
+                        ]
+                    },
+                    readwrite: {
+                        media_src_extensions: [
+                            { name: "ext2", namespaceURI: "http://myExtensions", value: "value2" }
+                        ],
+                        edit_media_extensions: [
+                            { name: "ext3", namespaceURI: "http://myExtensions", value: "value3" }
+                        ]
+                    },
+                    writeonly: {
+                        media_src_extensions: [],
+                        edit_media_extensions: [
+                            { name: "ext4", namespaceURI: "http://myExtensions", value: "value4" }
+                        ]
+                    }
+                }
+            },
+            readonly: {
+                __mediaresource: {
+                    media_src: "http://services.odata.org/OData/OData.svc/readonly",
+                    content_type: "image/png"
+                }
+            },
+            readwrite: {
+                __mediaresource: {
+                    media_src: "http://readwrite",
+                    edit_media: "http://readwrite/update",
+                    content_type: "image/gif",
+                    media_etag: "etag0"
+                }
+            },
+            writeonly: {
+                __mediaresource: {
+                    media_src: "http://services.odata.org/OData/OData.svc/writeonly",
+                    edit_media: "http://services.odata.org/OData/OData.svc/writeonly",
+                    content_type: "image/jpeg",
+                    media_etag: "etag1"
+                }
+            },
+            complexAsNamedStream1: {
+                __metadata: {},
+                __mediaresource: {
+                    media_src: "http://services.odata.org/OData/OData.svc/readonly",
+                    content_type: "image/png"
+                }
+            },
+            complexAsNamedStream2: {
+                __mediaresource: {
+                    p1: 500
+                }
+            }
+        };
+
+        var expectedEntry = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/entry id",
+                uri_extensions: [],
+                type: "the type",
+                type_extensions: [],
+                properties: {
+                    complexAsNamedStream1: {
+                        type: null,
+                        extensions: [],
+                        properties: {
+                            __mediaresource: {
+                                type: null,
+                                extensions: [],
+                                properties: {
+                                    content_type: {
+                                        extensions: [],
+                                        type: "Edm.String"
+                                    },
+                                    media_src: {
+                                        extensions: [],
+                                        type: "Edm.String"
+                                    }
+                                }
+                            }
+                        }
+                    },
+                    complexAsNamedStream2: {
+                        type: null,
+                        extensions: [],
+                        properties: {
+                            __mediaresource: {
+                                type: null,
+                                extensions: [],
+                                properties: {
+                                    p1: {
+                                        type: "Edm.String",
+                                        extensions: []
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            complexAsNamedStream1: {
+                __metadata: { type: null },
+                __mediaresource: {
+                    __metadata: { type: null },
+                    media_src: "http://services.odata.org/OData/OData.svc/readonly",
+                    content_type: "image/png"
+                }
+            },
+            complexAsNamedStream2: {
+                __metadata: { type: null },
+                __mediaresource: {
+                    __metadata: { type: null },
+                    p1: "500"
+                }
+            }
+        };
+
+        var request = { headers: {}, data: entry };
+        OData.atomHandler.write(request);
+
+        window.ODataReadOracle.readEntryLoopback(request.body,
+        function (expectedData) {
+            djstest.assertAreEqualDeep(expectedData, expectedEntry, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeComplexEntryTest() {
+        var entry = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/entry id",
+                uri_extensions: [],
+                type: "the type",
+                type_extensions: [],
+                properties: {
+                    Untyped: {
+                        type: "Edm.String",
+                        extensions: []
+                    },
+                    Typed: {
+                        type: "Edm.Int32",
+                        extensions: []
+                    },
+                    Complex: {
+                        type: "Complex",
+                        extensions: [],
+                        properties: {
+                            Property1: { type: "Edm.String", extensions: [] },
+                            Property2: { type: "Edm.String", extensions: [] },
+                            Nested: {
+                                type: "Complex.Nested",
+                                extensions: [],
+                                properties: {
+                                    NestedProperty1: { type: "Edm.String", extensions: [] },
+                                    NestedProperty2: { type: "Edm.String", extensions: [] },
+                                    NestedProperty3: { type: "Edm.String", extensions: [] }
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            Untyped: "untyped value",
+            Typed: 100,
+            Complex: {
+                __metadata: { type: "Complex" },
+                Property1: "value 1",
+                Property2: "value 2",
+                Nested: {
+                    __metadata: { type: "Complex.Nested" },
+                    NestedProperty1: "value 3",
+                    NestedProperty2: "value 4",
+                    NestedProperty3: "value 5"
+                }
+            }
+        };
+
+        var entryXml = OData.atomSerializer(OData.atomHandler, entry, {});
+
+        window.ODataReadOracle.readEntryLoopback(entryXml,
+        function (expectedData) {
+            djstest.assertAreEqualDeep(entry, expectedData, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeInlinedDeferredEntryTest() {
+        var entry = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/entry id",
+                uri_extensions: [],
+                type: "the type",
+                type_extensions: [],
+
+                properties: {
+                    deferred: {
+                        //the test oracle service always adds the title extension to the link
+                        extensions: [
+                           {
+                               name: "title",
+                               namespaceURI: null,
+                               value: null
+                           }
+                        ]
+                    },
+                    inline: {
+                        extensions: [
+                        {
+                            name: "title",
+                            namespaceURI: null,
+                            value: null
+                        }]
+                    }
+                }
+            },
+            deferred: {
+                __deferred: { uri: "http://deferred/" }
+            },
+            inline: {
+                __metadata: {
+                    uri: "http://services.odata.org/OData/OData.svc/entry id",
+                    uri_extensions: [],
+                    type: "the type",
+                    type_extensions: [],
+                    properties: {
+                        Untyped: {
+                            type: "Edm.String",
+                            extensions: []
+                        },
+                        Typed: {
+                            type: "Edm.Int32",
+                            extensions: []
+                        }
+                    }
+                },
+                Untyped: "untyped value",
+                Typed: 100
+            }
+        };
+
+        var entryXml = OData.atomSerializer(OData.atomHandler, entry, {});
+
+        window.ODataReadOracle.readEntryLoopback(entryXml,
+        function (expectedData) {
+            djstest.assertAreEqualDeep(entry, expectedData, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeJsObjectTest() {
+        var entryData = {
+            Color: 0x0000ff,
+            Height: 500,
+            Width: 100
+        };
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    Color: {
+                        type: "Edm.String",
+                        extensions: []
+                    },
+                    Width: {
+                        type: "Edm.String",
+                        extensions: []
+                    },
+                    Height: {
+                        type: "Edm.String",
+                        extensions: []
+                    }
+                }
+            },
+            Color: "255",
+            Height: "500",
+            Width: "100"
+        };
+
+        var entryXml = OData.atomSerializer(OData.atomHandler, entryData, {});
+
+        window.ODataReadOracle.readEntryLoopback(entryXml,
+        function (data) {
+            djstest.assertAreEqualDeep(entry, data, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializePrimitiveTypesTest() {
+        // Currently DateTime, Time and DateTimeOffset primitive types are not being covered.
+        // DateTimeOffset and Time are not covered because they aren't supported by current WCF DataServices implementations.
+        // DateTime follow non standard ways of being represented in JSON streams.
+
+        var testEntry = {
+            __metadata: {
+                properties: {
+                    Binary: {
+                        type: "Edm.Binary",
+                        extensions: []
+                    },
+                    Boolean: {
+                        type: "Edm.Boolean",
+                        extensions: []
+                    },
+                    Byte: {
+                        type: "Edm.Byte",
+                        extensions: []
+                    },
+                    Decimal: {
+                        type: "Edm.Decimal",
+                        extensions: []
+                    },
+                    Guid: {
+                        type: "Edm.Guid",
+                        extensions: []
+                    },
+                    Double: {
+                        type: "Edm.Double",
+                        extensions: []
+                    },
+                    Single: {
+                        type: "Edm.Single",
+                        extensions: []
+                    },
+                    Int16: {
+                        type: "Edm.Int16",
+                        extensions: []
+                    },
+                    Int32: {
+                        type: "Edm.Int32",
+                        extensions: []
+                    },
+                    Int64: {
+                        type: "Edm.Int64",
+                        extensions: []
+                    },
+                    SByte: {
+                        type: "Edm.SByte",
+                        extensions: []
+                    }
+                }
+            },
+            Binary: "01007A8A680D9E14A64EAC1242DD33C9DB05",
+            Boolean: false,
+            UntypedBoolean: "true",
+            Byte: 0xf0,
+            Decimal: "100.0",
+            Guid: "12345678-aaaa-bbbb-cccc-ddddeeeeffff",
+            Double: 1E+10,
+            Single: 100.01,
+            Int16: 16,
+            Int32: 32,
+            Int64: "64",
+            SByte: -8
+        };
+
+        var expectedEntry = {
+            __metadata: {
+                properties: {
+                    Binary: {
+                        type: "Edm.Binary",
+                        extensions: []
+                    },
+                    Boolean: {
+                        type: "Edm.Boolean",
+                        extensions: []
+                    },
+                    UntypedBoolean: {
+                        type: "Edm.String",
+                        extensions: []
+                    },
+                    Byte: {
+                        type: "Edm.Byte",
+                        extensions: []
+                    },
+                    Decimal: {
+                        type: "Edm.Decimal",
+                        extensions: []
+                    },
+                    Guid: {
+                        type: "Edm.Guid",
+                        extensions: []
+                    },
+                    Double: {
+                        type: "Edm.Double",
+                        extensions: []
+                    },
+                    Single: {
+                        type: "Edm.Single",
+                        extensions: []
+                    },
+                    Int16: {
+                        type: "Edm.Int16",
+                        extensions: []
+                    },
+                    Int32: {
+                        type: "Edm.Int32",
+                        extensions: []
+                    },
+                    Int64: {
+                        type: "Edm.Int64",
+                        extensions: []
+                    },
+                    SByte: {
+                        type: "Edm.SByte",
+                        extensions: []
+                    }
+                }
+            },
+            Binary: "01007A8A680D9E14A64EAC1242DD33C9DB05",
+            Boolean: false,
+            UntypedBoolean: "true",
+            Byte: 0xf0,
+            Decimal: "100.0",
+            Guid: "12345678-aaaa-bbbb-cccc-ddddeeeeffff",
+            Double: 1E+10,
+            Single: 100.01,
+            Int16: 16,
+            Int32: 32,
+            Int64: "64",
+            SByte: -8
+        };
+
+        var entryXml = OData.atomSerializer(OData.atomHandler, testEntry, {});
+
+        window.ODataReadOracle.readEntryLoopback(entryXml,
+        function (data) {
+            djstest.assertAreEqualDeep(data, expectedEntry, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeSpatialPropertyTest() {
+        var entry = {
+            __metadata: {
+                properties: {
+                    SpatialEdmTypeOnObjectWins: { type: "My.Type", extensions: [] },
+                    SpatialEdmTypeOnMetadata: { type: "Edm.GeometryPoint", extensions: [] }
+                }
+            },
+            SpatialEdmTypeOnly: {
+                __metadata: { type: "Edm.GeometryPoint" },
+                coordinates: [100, 200]
+            },
+            SpatialEdmTypeWins: {
+                __metadata: { type: "Edm.GeometryPoint" },
+                type: "MultiPoint",
+                coordinates: [300, 400]
+            },
+            SpatialAbstractType: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [500, 600]
+            },
+            SpatialEdmTypeOnObjectWins: {
+                __metadata: { type: "Edm.GeometryPoint" },
+                type: "Point",
+                coordinates: [5, 6]
+            },
+            SpatialEdmTypeOnMetadata: {
+                type: "Point",
+                coordinates: [7, 8]
+            },
+            SpatialUntyped: {
+                type: "Point",
+                coordinates: [9, 10]
+            }
+        };
+
+        var request = { headers: {}, data: entry };
+        OData.atomHandler.write(request, {});
+        var entryXml = request.body;
+
+        djstest.assertAreEqual("3.0", request.headers["DataServiceVersion"], "Request data service version is the expected one");
+
+        var response = {
+            headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" },
+            body: entryXml
+        };
+
+        OData.atomHandler.read(response, {});
+        var actual = response.data;
+
+        window.ODataReadOracle.readEntryLoopback(entryXml, function (expected) {
+            djstest.assertAreEqualDeep(actual, expected, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeSpatialPointTest() {
+        var entry = {
+            __metadata: {
+                properties: {
+                    pointEdmTypeOnObjectWins: { type: "Edm.GeographyPoint", extensions: [] },
+                    pointEdmTypeOnMetadata: { type: "Edm.GeometryPoint", extensions: [] }
+                }
+            },
+            geometryPoint: {
+                __metadata: { type: "Edm.GeometryPoint" },
+                type: "Point",
+                coordinates: [1, 2]
+            },
+            geographyPoint: {
+                __metadata: { type: "Edm.GeographyPoint" },
+                type: "Point",
+                coordinates: [3, 4]
+            },
+            emptyPoint: {
+                __metadata: { type: "Edm.GeometryPoint" },
+                type: "Point",
+                coordinates: []
+            }
+        };
+
+        var request = { headers: {}, data: entry };
+        OData.atomHandler.write(request, {});
+        var entryXml = request.body;
+
+        djstest.assertAreEqual("3.0", request.headers["DataServiceVersion"], "Request data service version is the expected one");
+
+        var response = {
+            headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" },
+            body: entryXml
+        };
+
+        OData.atomHandler.read(response, {});
+        var actual = response.data;
+
+        window.ODataReadOracle.readEntryLoopback(entryXml, function (expected) {
+            djstest.assertAreEqualDeep(actual, expected, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeSpatialLineStringTest() {
+        var entry = {
+            __metadata: {
+                properties: {
+                    lineStringTypeOnObjectWins: { type: "Edm.GeographyLineString", extensions: [] },
+                    lineStringTypeOnMetadata: { type: "Edm.GeometryLineString", extensions: [] }
+                }
+            },
+            geometryLineString: {
+                __metadata: { type: "Edm.GeometryLineString" },
+                type: "LineString",
+                coordinates: [[1, 2], [3, 4]]
+            },
+            geographyLineString: {
+                __metadata: { type: "Edm.GeographyLineString" },
+                type: "Point",
+                coordinates: [[5, 6], [7, 8]]
+            },
+            emptyLineString: {
+                __metadata: { type: "Edm.GeometryLineString" },
+                type: "LineString",
+                coordinates: []
+            },
+            lineStringTypeOnObjectWins: {
+                __metadata: { type: "Edm.GeometryLineString" },
+                type: "LineString",
+                coordinates: [[9, 10], [11, 12]]
+            },
+            lineStringTypeOnMetadata: {
+                type: "LineString",
+                coordinates: [[13, 14], [15, 16]]
+            }
+        };
+
+        var request = { headers: {}, data: entry };
+        OData.atomHandler.write(request, {});
+        var entryXml = request.body;
+
+        djstest.assertAreEqual("3.0", request.headers["DataServiceVersion"], "Request data service version is the expected one");
+
+        var response = {
+            headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" },
+            body: entryXml
+        };
+
+        OData.atomHandler.read(response, {});
+        var actual = response.data;
+
+        window.ODataReadOracle.readEntryLoopback(entryXml, function (expected) {
+            djstest.assertAreEqualDeep(actual, expected, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeSpatialPolygonTest() {
+        var entry = {
+            __metadata: {
+                properties: {
+                    polygonTypeOnObjectWins: { type: "Edm.GeographyPolygon", extensions: [] },
+                    polygonTypeOnMetadata: { type: "Edm.GeometryPolygon", extensions: [] }
+                }
+            },
+            geometryPolygon: {
+                __metadata: { type: "Edm.GeometryPolygon" },
+                type: "Polygon",
+                coordinates: [[[1, 2], [3, 4], [5, 6], [1, 2]]]
+            },
+            geographyPolygon: {
+                __metadata: { type: "Edm.GeographyPolygon" },
+                type: "Point",
+                coordinates: [[[7, 8], [9, 10], [11, 12], [7, 8]]]
+            },
+            emptyPolygon: {
+                __metadata: { type: "Edm.GeometryPolygon" },
+                type: "Point",
+                coordinates: []
+            },
+            polygonWithInternalRing: {
+                __metadata: { type: "Edm.GeometryPolygon" },
+                type: "Polygon",
+                coordinates: [[[13, 14], [15, 16], [17, 18], [13, 14]], [[19, 20], [21, 22], [23, 24], [19, 20]]]
+            },
+            polygonTypeOnObjectWins: {
+                __metadata: { type: "Edm.GeometryPolygon" },
+                type: "Polygon",
+                coordinates: [[[33, 34], [35, 36], [37, 38], [33, 34]]]
+            },
+            polygonTypeOnMetadata: {
+                type: "Polygon",
+                coordinates: [[[39, 40], [41, 42], [43, 44], [39, 40]]]
+            }
+        };
+
+        var request = { headers: {}, data: entry };
+        OData.atomHandler.write(request, {});
+        var entryXml = request.body;
+
+        djstest.assertAreEqual("3.0", request.headers["DataServiceVersion"], "Request data service version is the expected one");
+
+        var response = {
+            headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" },
+            body: entryXml
+        };
+
+        OData.atomHandler.read(response, {});
+        var actual = response.data;
+
+        window.ODataReadOracle.readEntryLoopback(entryXml, function (expected) {
+            djstest.assertAreEqualDeep(actual, expected, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeSpatialMultiPointTest() {
+        var entry = {
+            geometryMultiPoint: {
+                __metadata: { type: "Edm.GeometryMultiPoint" },
+                type: "MultiPoint",
+                coordinates: [[1, 2], [3, 4]]
+            },
+            geographyMultiPoint: {
+                __metadata: { type: "Edm.GeographyMultiPoint" },
+                type: "MultiPoint",
+                coordinates: [[5, 6], [7, 8]]
+            },
+            emptyMultiPoint: {
+                __metadata: { type: "Edm.GeometryMultiPoint" },
+                type: "MultiPoint",
+                coordinates: []
+            }
+        };
+
+        var request = { headers: {}, data: entry };
+        OData.atomHandler.write(request, {});
+        var entryXml = request.body;
+
+        djstest.assertAreEqual("3.0", request.headers["DataServiceVersion"], "Request data service version is the expected one");
+
+        var response = {
+            headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" },
+            body: entryXml
+        };
+
+        OData.atomHandler.read(response, {});
+        var actual = response.data;
+
+        window.ODataReadOracle.readEntryLoopback(entryXml, function (expected) {
+            djstest.assertAreEqualDeep(actual, expected, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeSpatialMultiLineStringTest() {
+        var entry = {
+            geometryMultiLineString: {
+                __metadata: { type: "Edm.GeometryMultiLineString" },
+                type: "MultiLineString",
+                coordinates: [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
+            },
+            geographyMultiLineString: {
+                __metadata: { type: "Edm.GeographyMultiLineString" },
+                type: "Point",
+                coordinates: [[[9, 10], [11, 12]]]
+            },
+            emptyMultiLineString: {
+                __metadata: { type: "Edm.GeometryMultiLineString" },
+                type: "MultiLineString",
+                coordinates: []
+            }
+        };
+
+        var request = { headers: {}, data: entry };
+        OData.atomHandler.write(request, {});
+        var entryXml = request.body;
+
+        djstest.assertAreEqual("3.0", request.headers["DataServiceVersion"], "Request data service version is the expected one");
+
+        var response = {
+            headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" },
+            body: entryXml
+        };
+
+        OData.atomHandler.read(response, {});
+        var actual = response.data;
+
+        window.ODataReadOracle.readEntryLoopback(entryXml, function (expected) {
+            djstest.assertAreEqualDeep(actual, expected, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeSpatialMultiLinePolygonTest() {
+        var entry = {
+            geometryMultiLineString: {
+                __metadata: { type: "Edm.GeometryMultiPolygon" },
+                type: "MultiPolygon",
+                coordinates: [[[[1, 2], [3, 4], [5, 6], [1, 2]]], [[[7, 8], [9, 10], [11, 12], [7, 8]]]]
+            },
+            geographyMultiLineString: {
+                __metadata: { type: "Edm.GeographyMultiPolygon" },
+                type: "MultiPolygon",
+                coordinates: [[[[9, 10], [11, 12], [13, 14], [9, 10]]]]
+            },
+            emptyMultiLineString: {
+                __metadata: { type: "Edm.GeometryMultiPolygon" },
+                type: "MultiPolygon",
+                coordinates: []
+            }
+        };
+
+        var request = { headers: {}, data: entry };
+        OData.atomHandler.write(request, {});
+        var entryXml = request.body;
+
+        djstest.assertAreEqual("3.0", request.headers["DataServiceVersion"], "Request data service version is the expected one");
+
+        var response = {
+            headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" },
+            body: entryXml
+        };
+
+        OData.atomHandler.read(response, {});
+        var actual = response.data;
+
+        window.ODataReadOracle.readEntryLoopback(entryXml, function (expected) {
+            djstest.assertAreEqualDeep(actual, expected, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeSpatialGeometryCollectionTest() {
+        var entry = {
+            geometryCollection: {
+                __metadata: { type: "Edm.GeometryCollection" },
+                type: "GeometryCollection",
+                geometries: [
+                    {
+                        type: "Point",
+                        coordinates: [1, 2]
+                    }
+                ]
+            },
+            geographyCollection: {
+                __metadata: { type: "Edm.GeometryCollection" },
+                type: "GeometryCollection",
+                geometries: [
+                    {
+                        type: "Point",
+                        coordinates: [3, 4]
+                    }
+                ]
+            },
+            emptyGeometryCollection: {
+                __metadata: { type: "Edm.GeometryCollection" },
+                type: "GeometryCollection",
+                geometries: []
+            },
+            nestedGeometryCollection: {
+                __metadata: { type: "Edm.GeometryCollection" },
+                type: "GeometryCollection",
+                geometries: [
+                    {
+                        type: "GeometryCollection",
+                        geometries: [
+                            {
+                                type: "GeometryCollection",
+                                geometries: [
+                                    {
+                                        type: "Point",
+                                        coordinates: [5, 6]
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+               ]
+            }
+        };
+
+        var request = { headers: {}, data: entry };
+        OData.atomHandler.write(request, {});
+        var entryXml = request.body;
+
+        djstest.assertAreEqual("3.0", request.headers["DataServiceVersion"], "Request data service version is the expected one");
+
+        var response = {
+            headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" },
+            body: entryXml
+        };
+
+        OData.atomHandler.read(response, {});
+        var actual = response.data;
+
+        window.ODataReadOracle.readEntryLoopback(entryXml, function (expected) {
+            djstest.assertAreEqualDeep(actual, expected, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function atomSerializeCollectionPropertiesTest() {
+        var entry = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/the id",
+                properties: {
+                    primitiveColArray: { type: "Collection(Edm.Int16)" },
+                    primitiveColObject: { type: "Collection(Edm.Int32)" },
+                    primitiveTypelessCollection: { type: "Collection()" },
+                    complexColArray: { type: "Collection(My.Type)" },
+                    complexColObject: { type: "Collection(My.Type2)" },
+                    colObjectTypeInObjectWins: { type: "Collection(Edm.Double)" },
+                    complexColArrayPropertiesInfo: {
+                        type: "Collection(My.Type3)",
+                        properties: {
+                            p3: { type: "Edm.Int32" },
+                            p4: { type: "Edm.Int16" }
+                        }
+                    }
+                }
+            },
+            primitiveColArray: [1, 2, 3, 4],
+            primitiveColObject: {
+                results: [5, 6, 7, 8]
+            },
+            primitiveColObjectWithType: {
+                __metadata: { type: "Collection(Edm.Double)" },
+                results: [1.1, 2.2, 3.3]
+            },
+            primitiveUntypedColArray: [5, 6, 7],
+            primitiveUntypedColObject: {
+                results: [10, 11, 12]
+            },
+            primitiveTypelessCollection: [13, 14, 15],
+            complexColArray: [{ p1: 10 }, { p1: 20}],
+            complexColObject: {
+                results: [{ p2: 300 }, { p2: 400}]
+            },
+            colObjectTypeInObjectWins: {
+                __metadata: { type: "Collection(Edm.Single)" },
+                results: [1.5, 2.5, 3.5]
+            },
+            complexColArrayPropertiesInfo: [
+               { p3: 900, p4: 1000 },
+               { p3: 900, p4: 1000 }
+            ],
+            complexTypelessCollection: {
+                __metadata: { type: "Collection()" },
+                results: [{ p5: 1500 }, { p5: 1600}]
+            }
+        };
+
+        var request = { headers: {}, data: entry };
+        OData.atomHandler.write(request, {});
+        var entryXml = request.body;
+
+        var response = {
+            headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" },
+            body: entryXml
+        };
+
+        OData.atomHandler.read(response, {});
+        var actual = response.data;
+
+        window.ODataReadOracle.readEntryLoopback(entryXml, function (expected) {
+            djstest.assertAreEqualDeep(actual, expected, "Response data not same as expected");
+            djstest.done();
+        });
+    });
+
+    //    djstest.addTest(function atomSerializeInvalidPayloadTest() {
+    //        djstest.expectException(function () {
+    //            OData.atomSerializer(OData.atomHandler, { EntitySets: [] });
+    //        }, "expected exception for serializing a service document");
+
+    //        djstest.expectException(function () {
+    //            OData.atomSerializer(OData.atomHandler, null, {});
+    //        }, "expected exception for serializing null data");
+
+    //        djstest.expectException(function () {
+    //            OData.atomSerializer(OData.atomHandler, undefined, {});
+    //        }, "expected exception for serializing undefined data");
+
+    //        djstest.done();
+    //    });
+
+    djstest.addTest(function atomSetEntryValueByPathTest() {
+        var target = {};
+
+        // simple property added.
+        OData.atomSetEntryValueByPath("p", target, 1);
+        djstest.assertAreEqual(target.p, 1, "target.p set to 1");
+
+        // simple property replaced.
+        OData.atomSetEntryValueByPath("p", target, 2);
+        djstest.assertAreEqual(target.p, 2, "target.p overwritten to 1");
+
+        // Construct object and set property.
+        OData.atomSetEntryValueByPath("a/b", target, 3);
+        djstest.assertAreEqual(target.a.b, 3, "target.a.b set to 3");
+
+        // Construct object and replace property.
+        OData.atomSetEntryValueByPath("a/b", target, 4);
+        djstest.assertAreEqual(target.a.b, 4, "target.a.b set to 4");
+
+        // Construct object deeply and set property.
+        OData.atomSetEntryValueByPath("a/deep/value", target, 5);
+        djstest.assertAreEqual(target.a.b, 4, "target.a.b still 4");
+        djstest.assertAreEqual(target.a.deep.value, 5, "target.a.deep.value set to 5");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function getXmlPathValueTest() {
+        // Tests with expected value and result based on doc.
+        var doc = datajs.xmlParse(
+        "<atom:entry xmlns:atom='http://www.w3.org/2005/Atom' xmlns:c='custom'>" +
+        " <atom:title>title</atom:title>" +
+        " <atom:summary>summary</atom:summary>" +
+        " <atom:author><atom:name>author name</atom:name><atom:uri>author uri</atom:uri><atom:email>author email</atom:email></atom:author>" +
+        " <atom:contributor><atom:name>contributor name</atom:name></atom:contributor>" +
+        " <c:element>custom element</c:element>" +
+        " <c:other c:with-attribute='attribute' with-attribute='decoy'>text</c:other>" +
+        " <c:third><c:item><c:a>a</c:a><c:b>b</c:b><c:empty /></c:item></c:third>" +
+        "</atom:entry>");
+
+        var root = datajs.xmlFirstChildElement(doc);
+
+        var atomXmlNs = "http://www.w3.org/2005/Atom";
+        var tests = [
+        { ns: atomXmlNs, path: "title", e: "title" },
+        { ns: atomXmlNs, path: "updated", e: null },
+        { ns: atomXmlNs, path: "summary", e: "summary" },
+        { ns: atomXmlNs, path: "rights", e: null },
+        { ns: atomXmlNs, path: "published", e: null },
+        { ns: atomXmlNs, path: "contributor/email", e: null },
+        { ns: atomXmlNs, path: "contributor/uri", e: null },
+        { ns: atomXmlNs, path: "contributor/name", e: "contributor name" },
+        { ns: atomXmlNs, path: "author/uri", e: "author uri" },
+        { ns: atomXmlNs, path: "author/name", e: "author name" },
+        { ns: atomXmlNs, path: "author/email", e: "author email" },
+        { ns: "custom", path: "element", e: "custom element" },
+        { ns: "other", path: "element", e: null },
+        { ns: "custom", path: "other", e: "text" },
+        { ns: "custom", path: "other/@with-attribute", e: "attribute" },
+        { ns: "custom", path: "third/item/a", e: "a" },
+        { ns: "custom", path: "third/item/b", e: "b" },
+        { ns: "custom", path: "third/item/c", e: null },
+        { ns: "custom", path: "third/item/empty", e: null }
+    ];
+
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            var test = tests[i];
+            var node = datajs.xmlFindNodeByPath(root, test.ns, test.path);
+            var actual = node && datajs.xmlNodeValue(node);
+            djstest.assertAreEqual(actual, test.e, "match for test #" + i + "(" + test.path + ")");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomApplyCustomizationToEntryObjectTest() {
+        var entryXml =
+            "<atom:entry xmlns:atom='http://www.w3.org/2005/Atom' xmlns:c='custom'>" +
+            " <atom:title>title</atom:title>" +
+            " <atom:summary>summary</atom:summary>" +
+            " <atom:author><atom:name>author name</atom:name><atom:uri>author uri</atom:uri><atom:email>author email</atom:email></atom:author>" +
+            " <atom:contributor><atom:name>contributor name</atom:name></atom:contributor>" +
+            " <atom:category term='Ns.Customer' scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme' />" +
+            " <c:element>custom element</c:element>" +
+            " <c:other c:with-attribute='attribute' with-attribute='decoy'>text</c:other>" +
+            " <c:third><c:item><c:a>a</c:a><c:b>b</c:b><c:empty /></c:item></c:third>" +
+            "</atom:entry>"
+
+        var tests = [
+            { ns: null, path: "SyndicationTitle", e: "title" },
+            { ns: null, path: "SyndicationUpdated", e: undefined },
+            { ns: null, path: "SyndicationSummary", e: "summary" },
+            { ns: "custom", path: "element", e: "custom element" },
+            { ns: "custom", path: "other/@with-attribute", e: "attribute" }
+        ];
+
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            var test = tests[i];
+            var model = {
+                "namespace": "Ns",
+                entityType: [
+                    {
+                        name: "Customer",
+                        FC_SourcePath: "targetProperty/value",
+                        FC_NsUri: test.ns, FC_TargetPath: test.path
+                    }
+                ]
+            };
+            var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+            OData.atomHandler.read(response, { metadata: model });
+            var data = response.data;
+            var actual = (data.targetProperty) ? data.targetProperty.value : undefined;
+            djstest.assertAreEqual(actual, test.e, "match for test #" + i + "(" + test.path + ")");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadCustomizedEntryWithNoTypeInformation() {
+        var entryXml =
+        "<atom:entry xmlns:atom='http://www.w3.org/2005/Atom' xmlns:c='custom'>" +
+        " <atom:title>title</atom:title>" +
+        " <atom:summary>summary</atom:summary>" +
+        " <atom:author><atom:name>author name</atom:name><atom:uri>author uri</atom:uri><atom:email>author email</atom:email></atom:author>" +
+        " <atom:contributor><atom:name>contributor name</atom:name></atom:contributor>" +
+        " <c:element>custom element</c:element>" +
+        " <c:other c:with-attribute='attribute' with-attribute='decoy'>text</c:other>" +
+        " <c:third><c:item><c:a>a</c:a><c:b>b</c:b><c:empty /></c:item></c:third>" +
+        "</atom:entry>";
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+        var metadata = { "namespace": "Ns", entityType: [{ name: "Customer"}] };
+
+        OData.atomHandler.read(response, { metadata: metadata });
+        djstest.assertAreEqualDeep(response.data, { __metadata: {} }, "No change for entity with no type name");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadCustomizedEntryWithMismatchedType() {
+        var entryXml =
+        "<atom:entry xmlns:atom='http://www.w3.org/2005/Atom' xmlns:c='custom'>" +
+        " <atom:title>title</atom:title>" +
+        " <atom:summary>summary</atom:summary>" +
+        " <atom:author><atom:name>author name</atom:name><atom:uri>author uri</atom:uri><atom:email>author email</atom:email></atom:author>" +
+        " <atom:contributor><atom:name>contributor name</atom:name></atom:contributor>" +
+        " <atom:category term='Ns.Customer' scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme' />" +
+        " <c:element>custom element</c:element>" +
+        " <c:other c:with-attribute='attribute' with-attribute='decoy'>text</c:other>" +
+        " <c:third><c:item><c:a>a</c:a><c:b>b</c:b><c:empty /></c:item></c:third>" +
+        "</atom:entry>";
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+        var metadata = { "namespace": "Ns", entityType: [{ name: "Person"}] };
+
+        OData.atomHandler.read(response, { metadata: metadata });
+        djstest.assertAreEqualDeep(response.data,
+            { __metadata: { type: "Ns.Customer", type_extensions: []} },
+            "No change for entity with no matching type name");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadCustomizedEntryTest() {
+        var entryXml =
+        "<atom:entry xmlns:atom='http://www.w3.org/2005/Atom' xmlns:c='custom'>" +
+        " <atom:title>title</atom:title>" +
+        " <atom:summary>summary</atom:summary>" +
+        " <atom:author><atom:name>author name</atom:name><atom:uri>author uri</atom:uri><atom:email>author email</atom:email></atom:author>" +
+        " <atom:contributor><atom:name>contributor name</atom:name></atom:contributor>" +
+        " <atom:category term='Ns.Customer' scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme' />" +
+        " <c:element>custom element</c:element>" +
+        " <c:other c:with-attribute='attribute' with-attribute='decoy'>text</c:other>" +
+        " <c:third><c:item><c:a>a</c:a><c:b>b</c:b><c:empty /></c:item></c:third>" +
+        "</atom:entry>";
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+        var metadata;
+
+        // Entity-level feed customization.
+        metadata = { "namespace": "Ns", entityType: [{ name: "Customer", FC_SourcePath: "name", FC_TargetPath: "SyndicationTitle"}] };
+        OData.atomHandler.read(response, { metadata: metadata });
+        djstest.assertAreEqualDeep(response.data, {
+            __metadata: { type: "Ns.Customer", type_extensions: [] },
+            name: "title"
+        }, "title mapped from entity");
+
+        // Property-level feed customization.
+        metadata = {
+            "namespace": "Ns",
+            entityType: [
+                {
+                    name: "Customer",
+                    FC_SourcePath: "name",
+                    FC_TargetPath:
+                    "SyndicationTitle",
+                    property: [
+                        { name: "biography", FC_TargetPath: "SyndicationSummary" },
+                        { name: "stuff", FC_TargetPath: "third/item/a", FC_NsUri: "custom" },
+                        { name: "contactInfo", FC_SourcePath: "email", FC_TargetPath: "SyndicationAuthorEmail" }
+                    ]
+                }
+            ]
+        };
+
+        OData.atomHandler.read(response, { metadata: metadata });
+        djstest.assertAreEqualDeep(response.data, {
+            __metadata: { type: "Ns.Customer", type_extensions: [] },
+            name: "title",
+            biography: "summary",
+            "stuff": "a",
+            contactInfo: { email: "author email" }
+        }, "biography and stuff mapped from property feed customizations");
+
+        // Base type customization.
+        metadata = {
+            "namespace": "Ns",
+            entityType: [
+                {
+                    name: "Customer", baseType: "Ns.Person",
+                    FC_SourcePath: "name", FC_TargetPath: "SyndicationTitle",
+                    property: [
+                        { name: "biography", FC_TargetPath: "SyndicationSummary" },
+                        { name: "stuff", FC_TargetPath: "third/item/a", FC_NsUri: "custom" }
+                    ]
+                },
+                {
+                    name: "Person",
+                    FC_SourcePath: "name", FC_TargetPath: "SyndicationTitle",
+                    property: [
+                        { name: "personAttribute", FC_TargetPath: "other/@with-attribute", FC_NsUri: "custom" }
+                    ]
+                }
+            ]
+        };
+
+        OData.atomHandler.read(response, { metadata: metadata });
+        djstest.assertAreEqualDeep(response.data, {
+            __metadata: { type: "Ns.Customer", type_extensions: [] },
+            name: "title",
+            biography: "summary",
+            "stuff": "a",
+            personAttribute: "attribute"
+        }, "attribute mapped from base type");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function lookupPropertyTypeTest() {
+        // Tests are in metadata / entity / path / expected name form.
+
+        var schema = {
+            namespace: "Ns",
+            entityType: [
+            { name: "Person", property: [
+                { name: "id", type: "Edm.Int32" }, { name: "name" }
+            ]
+            },
+            { name: "Customer", baseType: "Ns.Person", property: [
+                { name: "account", type: "Edm.Int32" }, { name: "delivery", type: "Ns.Address" }
+            ]
+            }
+        ],
+            complexType: [
+            { name: "Address", property: [{ name: "street", type: "Edm.String" }, { name: "city"}] }
+        ]
+        };
+
+        var tests = [
+        { m: schema, e: schema.entityType[0], path: "id", n: "Edm.Int32" },
+        { m: schema, e: schema.entityType[0], path: "foo", n: null },
+        { m: schema, e: schema.entityType[0], path: "name", n: null },
+        { m: schema, e: schema.entityType[1], path: "id", n: "Edm.Int32" },
+        { m: schema, e: schema.entityType[1], path: "name", n: null },
+        { m: schema, e: schema.entityType[1], path: "account", n: "Edm.Int32" },
+        { m: schema, e: schema.entityType[1], path: "delivery/street", n: "Edm.String" }
+    ];
+
+        var i, len;
+        for (i = 0, len = tests.length; i < len; i++) {
+            var test = tests[i];
+            var actual = OData.lookupPropertyType(test.m, test.e, test.path);
+            djstest.assertAreEqualDeep(actual, test.n, "matching type name for path " + test.path);
+        }
+
+        djstest.done();
+    });
+
+    var dateMs = function (text, extraMs, ns, offset) {
+        /// <summary>Constructs a Date object by parsing the specified text and adding milliseconds, nanosenconds and offest data.</summary>
+        /// <param name="text" type="String">Date in string form.</param>
+        /// <param name="extraMS" type="Number">Milliseconds to add.</param>
+        /// <param name="ns" type="Number">Nanoseconds.</param>
+        /// <param name="offset" type="String">Offset data.</param>
+
+        var result = new Date(new Date(text).valueOf() + extraMs);
+        if (ns !== undefined) {
+            result.__ns = ns;
+        }
+
+        if (offset) {
+            result.__edmType = "Edm.DateTimeOffset";
+            result.__offset = offset;
+        }
+
+        return result;
+    };
+
+    djstest.addTest(function parseDateTimeTest() {
+
+        // This function is used to create dates with out-of-range years; it works only on Safari
+        var createDateWithLargeYear = function (dateString, actualYear) {
+            var date = new Date(dateString);
+            date.setFullYear(actualYear);
+
+            if (!isNaN(date.valueOf())) {
+                return date;
+            }
+            else {
+                return undefined;
+            }
+        };
+
+        // Input and expected values (undefined for exceptions).
+        var tests = [
+        // Valid values.
+            {i: "2000-01-02T03:04:05", e: new Date("01/02/2000 03:04:05 GMT") }, // simple date
+            {i: "2000-01-31T03:04:05", e: new Date("01/31/2000 03:04:05 GMT") }, // 31st date of month
+            {i: "2000-01-31T23:58:59", e: new Date("01/31/2000 23:58:59 GMT") }, // post-noon hours
+            {i: "0500-08-09T09:09:09", e: new Date("08/09/0500 09:09:09 GMT") }, // leading zeroes
+            {i: "20000-01-31T23:58:59", e: new Date("01/31/20000 23:58:59 GMT") }, // >4 digit years
+            {i: "2000-01-02T24:00:00", e: new Date("01/03/2000 00:00:00 GMT") }, // 24:00 for midnight
+            {i: "0050-01-31T23:00:00", e: new Date(-60586621200000) }, // year in 0-99 range
+
+            {i: "2000-01-31T03:04:05.5", e: dateMs("01/31/2000 03:04:05 GMT", 500) },  // An extra half-second
+            {i: "2000-01-31T03:04:05.001", e: dateMs("01/31/2000 03:04:05 GMT", 1) },  // An extra millisecond
+            {i: "2000-01-31T03:04:05.0000001", e: dateMs("01/31/2000 03:04:05 GMT", 0, 1) },  // An extra nanosecond
+            {i: "2000-01-31T03:04:05.0010001", e: dateMs("01/31/2000 03:04:05 GMT", 1, 1) },  // An extra millisecond and nanosecond
+            {i: "2000-01-31T03:04:05.0011000", e: dateMs("01/31/2000 03:04:05 GMT", 1, 1000) },  // An extra millisecond and 1000 nanoseconds
+            {i: "2000-01-31T03:04:05.00101", e: dateMs("01/31/2000 03:04:05 GMT", 1, 100) },  // An extra millisecond and 100 nanoseconds
+            {i: "2011-09-24T16:20:15.7724193", e: dateMs("09/24/2011 16:20:15 GMT", 772, 4193) }, // Azure timestamp value
+            {i: "-0100-01-31T23:58:59", e: new Date(-65288678461000) },                 // BC years
+            {i: "2000-02-29T03:04:05", e: new Date("02/29/2000 03:04:05 GMT") },  // Leap day
+            {i: "0000-01-02T03:04:05", e: new Date(-62135499355000) },   // Zero year
+            {i: "2000-13-32T25:61:61", e: new Date("02/02/2001 02:02:01 GMT") },  // Time components overflow
+            {i: "2000-01-02T24:00:01", e: new Date("01/03/2000 00:00:01 GMT") },  // 24-hour overflow
+            {i: "2000-04-31T00:00:00", e: new Date("05/01/2000 00:00:00 GMT") },  // Day overflow
+            {i: "2001-02-29T00:00:00", e: new Date("03/01/2001 00:00:00 GMT") },  // Non-existent leap day overflow
+            {i: "2000-01-02T03:04", e: new Date("01/02/2000 03:04:00 GMT") },  //Date without seconds
+        // Unparseable invalid values.
+            {i: "2000-01-02T03:04:05+01:00" }, // Timezone included
+            {i: "2000-01-02T03:04:05.00000001" },  // Too much precision, tenths of nanoseconds not supported
+            {i: "-100-01-31T23:58:59" },       // Year component too short
+            {i: "2000" },                      // Not enough components
+            {i: "+2000-01-02T03:04:05" },      // Positive sign prefix
+            {i: "2000-1-2T3:4:5" },            // Components too short
+            {i: "2000-01-02T03:04:05." },      // Trailing dot with no fractional seconds component
+
+        // Out of range date values (except Safari)
+            {i: "-271822-04-19T23:59:59", e: createDateWithLargeYear("04/19/2000 23:59:59 GMT", -271821) },
+            { i: "275760-09-13T00:00:01", e: createDateWithLargeYear("09/13/2000 00:00:01 GMT", 275760) }
+        ];
+
+        for (var i = 0; i < tests.length; i++) {
+            var result;
+            try {
+                result = OData.parseDateTime(tests[i].i);
+            } catch (err) {
+                result = undefined;
+            }
+
+            djstest.assertAreEqualDeep(result, tests[i].e, "parseDateTime for " + tests[i].i + " expecting " + tests[i].e + " found " + result);
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function parseDateTimeOffsetTest() {
+        // Each test has input, expected value in text form, and expected offset text.
+        var tests = [
+        // Valid values.
+            {i: "2000-01-02T03:04:05Z", ev: dateMs("01/02/2000 03:04:05 GMT", 0, 0, "Z") }, // simple date
+            {i: "2000-01-02T03:04:05+00:00", ev: dateMs("01/02/2000 03:04:05 GMT", 0, 0, "Z") }, // simple date with zero positive offset
+            {i: "2000-01-02T03:04:05-00:00", ev: dateMs("01/02/2000 03:04:05 GMT", 0, 0, "Z") }, // simple date with zero negative offset
+            {i: "2000-01-02T03:04:05+14:00", ev: dateMs("01/01/2000 13:04:05 GMT", 0, 0, "+14:00") }, // simple date with maximum positive offset
+            {i: "2000-01-02T03:04:05-14:00", ev: dateMs("01/02/2000 17:04:05 GMT", 0, 0, "-14:00") }, // simple date with maximum negative offset
+            {i: "2000-01-02T03:04:05+01:00", ev: dateMs("01/02/2000 02:04:05 GMT", 0, 0, "+01:00") }, // simple date with an hour positive offset
+            {i: "2000-01-02T03:04:05+00:30", ev: dateMs("01/02/2000 02:34:05 GMT", 0, 0, "+00:30") }, // simple date with a half-hour positive offset
+            {i: "2000-01-02T03:04:05-01:00", ev: dateMs("01/02/2000 04:04:05 GMT", 0, 0, "-01:00") }, // simple date with an hour negative offset
+            {i: "2000-01-02T03:04:05+10:30", ev: dateMs("01/01/2000 16:34:05 GMT", 0, 0, "+10:30") }, // simple date with a ten hour and a half positive offset
+            {i: "2000-01-02T03:04:05-10:30", ev: dateMs("01/02/2000 13:34:05 GMT", 0, 0, "-10:30") }, // simple date with a ten hour and a half negative offset
+            {i: "2000-01-01T00:29:00+00:30", ev: dateMs("12/31/1999 23:59:00 GMT", 0, 0, "+00:30") }, // positive offset crossing component boundaries
+            {i: "1999-12-31T23:30:00-00:30", ev: dateMs("01/01/2000 00:00:00 GMT", 0, 0, "-00:30") },  // negative offset crossing component boundaries
+            {i: "2011-09-24T16:20:15.7724193Z", ev: dateMs("09/24/2011 16:20:15 GMT", 772, 4193, "Z")} // Azure timestamp value
+        ];
+
+        for (var i = 0; i < tests.length; i++) {
+            var result = OData.parseDateTimeOffset(tests[i].i);
+            djstest.assertAreEqualDeep(result, tests[i].ev, "parseDateTimeOffset for " + tests[i].i);
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function parseTimeTest() {
+        var error;
+        try {
+            OData.parseTime("00:01:02");
+            error = null;
+        } catch (err) {
+            error = err;
+        }
+
+        djstest.assert(error !== null, "error !== null");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function formatDateTimeOffsetTest() {
+        // Input values that should simply round-trip.
+        var tests = [
+            "2000-01-02T03:04:05Z",
+            "2000-01-31T03:04:05Z",
+            "-2000-01-31T03:04:05.002Z",
+            "2000-01-31T03:04:05.800+00:10-00:00",
+            "2000-01-31T03:04:05.020+00:10+00:00",
+            "2000-01-31T03:04:05.500+00:10-08:30",
+            "2000-01-31T03:04:05.500+12:10+08:30",
+            "2000-01-31T03:04:05.500-12:10-08:30",
+            "0099-01-31T03:04:05.500-12:10+00:30",
+            "-0001-01-31T03:04:05.500-12:10+00:05",
+            "-0001-01-31T03:04:05.500-12:10-00:05",
+            "2020-01-31T03:04:05.501-12:10+07:00",
+            "2011-09-24T16:20:15.7724193Z"
+        ];
+
+        for (var i = 0; i < tests.length; i++) {
+            var test = tests[i];
+            var dateValue = OData.parseDateTimeOffset(test);
+            var textValue = OData.formatDateTimeOffset(dateValue);
+            djstest.assertAreEqual(textValue, test, "Roundtripping " + test + " through " + dateValue.toUTCString());
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function malformedXmlTest() {
+        // Verifies that malformed XML documents (typically incomplete payloads) do indeed throw an error.
+        var xmlText = "<top>text <another /> <item> </item> <!-- top not closed -->";
+        var err = null;
+        try {
+            parseMetadataHelper(xmlText);
+        } catch (_) { err = _; }
+
+        var expectedErrorMessage0 = "The following tags were not closed: top."; // IE MSXML
+        var expectedErrorMessage1 = "XML Parsing Error: no element found";      // FF
+        var expectedErrorMessage2 = "This page contains the following errors";  // Chrome
+        var expectedErrorMessage3 = "Error";                                    // Opera
+        var expectedErrorMessage4 = "DOM Exception: SYNTAX_ERR (12)"            // IE9 DOMParser
+        var expectedErrorMessage5 = "SyntaxError"                               // IE10 DOMParser
+
+        djstest.assert(err !== null, "err !== null");
+        djstest.assertAreEqual(err.errorXmlText, xmlText, "xmlText matches original");
+        djstest.assert(
+            err.message.indexOf(expectedErrorMessage0) === 0 ||
+            err.message.indexOf(expectedErrorMessage1) === 0 ||
+            err.message.indexOf(expectedErrorMessage2) === 0 ||
+            err.message.indexOf(expectedErrorMessage3) === 0 && window.opera ||
+            err.message.indexOf(expectedErrorMessage4) === 0 ||
+            err.message.indexOf(expectedErrorMessage5) === 0,
+            err.message + " contains one of '" +
+            [expectedErrorMessage0, expectedErrorMessage1, expectedErrorMessage2,
+             expectedErrorMessage3, expectedErrorMessage4, expectedErrorMessage5].join(',') + "'");
+
+        djstest.done();
+    });
+
+    // DATAJS INTERNAL END
+})(this);
\ No newline at end of file
diff --git a/JSLib/tests/odata-qunit-tests.htm b/JSLib/tests/odata-qunit-tests.htm
new file mode 100644
index 0000000..7f3248d
--- /dev/null
+++ b/JSLib/tests/odata-qunit-tests.htm
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<html>
+<!--
+Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-->
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
+    <meta http-equiv="cache-control" content="no-cache" />
+    <meta http-equiv="pragma" content="no-cache" />
+    <meta http-equiv="expires" content="-1" />
+
+    <title>OData unit tests</title>
+    <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.10.0.css" type="text/css" />
+    
+    <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.4.min.js"></script>
+    <!--
+    <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.4.js"></script>
+    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.2.js"></script>
+    -->
+
+    <script type="text/javascript" src="http://code.jquery.com/qunit/qunit-1.10.0.js"></script>
+    <script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js"></script>
+
+    <script type="text/javascript" src="common/ODataReadOracle.js"></script>
+    <script type="text/javascript" src="common/TestSynchronizerClient.js"></script>
+
+
+    <script type="text/javascript">
+        window.TestSynchronizer.init(QUnit);
+    </script>
+   
+    <script type="text/javascript" src="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="../src/store-dom.js"></script>
+    <script type="text/javascript" src="../src/store-indexeddb.js"></script>
+    <script type="text/javascript" src="../src/store-memory.js"></script>
+    <script type="text/javascript" src="../src/store.js"></script>
+  
+    <script type="text/javascript" src="../src/deferred.js"></script>
+    <script type="text/javascript" src="../src/cache-source.js"></script>
+    <script type="text/javascript" src="../src/cache.js"></script>
+    
+    <script type="text/javascript" src="common/djstest.js"></script>
+
+    <script type="text/javascript" src="odata-atom-tests.js"></script>
+
+  </head>
+  <body>
+    <h1 id="qunit-header">OData Unit Tests</h1>
+    <h2 id="qunit-banner"></h2>
+    <h2 id="qunit-userAgent"></h2>
+    <ol id="qunit-tests">
+    </ol>
+  </body>
+</html>
\ No newline at end of file
diff --git a/JSLib/tests/run-tests.wsf b/JSLib/tests/run-tests.wsf
new file mode 100644
index 0000000..26d954d
--- /dev/null
+++ b/JSLib/tests/run-tests.wsf
@@ -0,0 +1,427 @@
+<!--
+Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-->
+<job>
+    <runtime>
+        <description>Test driver for running datajs tests - run from the same directory as the script</description>
+        <comment>
+            Result codes:
+            0 - success
+            1 - failed to launch tests
+            2 - tests failed
+        </comment>
+    </runtime>
+    <script language="JScript" src="test-list.js" />
+    <script language="JScript">
+
+        var exitCode;
+        var fso = WScript.CreateObject("Scripting.FileSystemObject");
+        var shell = WScript.CreateObject("WScript.Shell");
+
+        function attempt(action, interval, maxAttempts) {
+            /// <summary>Attempt an action at an interval, optionally for a maximum number of attempts</summary>
+            /// <param name="action">Action callback; should return boolean whether it succeeded</param>
+            /// <param name="interval">Interval (milliseconds) between attempts</param>
+            /// <param name="maxAttempts">(Optional) Maximum number of attempts. Infinite if undefined.</param>
+            /// <returns>Whether the action succeeded</returns>
+            var done = false;
+            var attempts = 0;
+            while (!done) {
+                var success = action();
+                if (maxAttempts !== undefined) {
+                    attempts++;
+                }
+                done = success === true || (maxAttempts !== undefined && attempts >= maxAttempts);
+                if (!done) {
+                    WScript.Sleep(interval);
+                }
+            }
+
+            return success;
+        }
+
+        function parseJson(text) {
+            /// <summary>Parses a JSON document, removes the 'd' wrapper.</summary>
+            try {
+                return eval("(" + text + ")").d;
+            } catch (e) {
+                throw { message: "Error parsing JSON: [" + text + "]" };
+            }
+        }
+
+        function SaveTextToFile(content, path) {
+            /// <summary>Saves text content into a file.</summary>
+            /// <param name="content" type="String">Content to save.</param>
+            /// <param name="path" type="String">Path of file to save into.</param>
+            var ForReading = 1, ForWriting = 2;
+            var file = fso.OpenTextFile(path, ForWriting, true, -1 /* open as unicode */);
+            file.Write(content);
+            file.Close();
+        }
+
+        function GetUrlSync(url) {
+            var xhr;
+            xhr = WScript.CreateObject("Msxml2.ServerXMLHTTP.6.0");
+            xhr.open("GET", url, false);
+            xhr.send();
+            return xhr.responseText;
+        }
+
+        function LaunchBrowser(browsers, serviceRoot, followingPages, url, testRunId, outputDirectory) {
+            /// <summary>Launches a browsers and waits until the service tells us the run is complete.</summary>
+            /// <param name="browsers">Browsers to run.</param>
+            /// <param name="serviceRoot" type="String">Root URL of the logging service.</param>
+            /// <param name="followingPages" type="Array">Array of pages that should follow the given url.</param>
+            /// <param name="url" type="String">URL of the page to start the browser on.</param>
+            /// <param name="testRunId" type="String">ID of the test run being monitored.</param>
+            /// <param name="outputDirectory" type="String">Directory in which to output screenshots.</param>
+
+            for (browserName in browsers) {
+                var xhr;
+                var markInProgressUrl = serviceRoot + "MarkInProgress?testRunId=" + testRunId;
+                GetUrlSync(markInProgressUrl);
+
+                // Add all the pages that follow the given URL.
+                if (followingPages && followingPages.length > 0) {
+                    var addFilesUrl = serviceRoot + "AddTestPages?testRunId=" + testRunId + "&pages=" + followingPages.join();
+                    GetUrlSync(addFilesUrl);
+                }
+
+                var setPrefixUrl = serviceRoot + "SetTestNamePrefix?testRunId=" + testRunId + "&prefix=" + browserName + "-";
+                GetUrlSync(setPrefixUrl);
+
+                exitCode = 0;
+                var response;
+
+                // Only the first location found from the browsers array is used. If none of the listed locations of the browser exist and the browser argument was 
+                // explicitly used then an exception is thrown.
+                var browserFound = false;
+                for (var i = 0; i < browsers[browserName].length && !browserFound; i++) {
+                    var path = shell.ExpandEnvironmentStrings(browsers[browserName][i]);
+                    if (fso.FileExists(path)) {
+                        browserFound = true;
+
+                        WScript.Echo("Navigating to " + url + " with " + path);
+                        var browser = shell.Exec("\"" + path + "\" " + url);
+
+                        var checkRunUrl = serviceRoot + "IsTestRunInProgress?testRunId=" + testRunId;
+                        WScript.Echo("Monitoring status on " + checkRunUrl);
+
+                        var interval = 2000;
+                        var maxAttempts = WScript.Arguments.Named.Exists("timeout") ? Math.floor((WScript.Arguments.Named.Item("timeout") / interval) * 1000) : undefined;
+                        var success = attempt(function () {
+                            return parseJson(GetUrlSync(checkRunUrl)) !== true;
+                        }, interval, maxAttempts);
+                        if (!success) {
+                            WScript.Echo("Timed out waiting for test to complete");
+                            exitCode = 2;
+                        }
+
+                        RunCommand("taskkill.exe /pid " + browser.ProcessID, true);
+                    }
+                }
+
+                // If the "/browsers" argument was explicitly used and all location have been checked, then throw an exception.
+                if (!browserFound) {
+                    var message = "Unable to find browser at: " + path;
+                    if (WScript.Arguments.Named.Exists("browsers")) {
+                        throw { message: message };
+                    } else {
+                        WScript.Echo(message);
+                    }
+                }
+            }
+        }
+
+        function WriteTestRunResults(serviceRoot, testRunId, outputDirectory) {
+            /// <summary>Writes the results of the test run to disk and updates the overall status.</summary>
+            /// <param name="serviceRoot" type="String">Root URL of the logging service.</param>
+            /// <param name="testRunId" type="String">ID of the test run being monitored.</param>
+            /// <param name="outputDirectory" type="String">Directory in which to write test result files.</param>
+
+            var getResultsUrl = serviceRoot + "GetTestRunResults?testRunId=" + testRunId;
+            WScript.Echo("Querying " + getResultsUrl);
+
+            var response = GetUrlSync(getResultsUrl);
+
+            var resultsPath = outputDirectory + "\\results.trx";
+            WScript.Echo("Writing results.trx file to " + resultsPath);
+            SaveTextToFile(response, resultsPath);
+
+            var xml = new ActiveXObject("Msxml2.DOMDocument.6.0");
+            xml.loadXML(response);
+            xml.setProperty("SelectionNamespaces", "xmlns:trx='http://microsoft.com/schemas/VisualStudio/TeamTest/2010'");
+            xml.setProperty("SelectionLanguage", "XPath");
+            var resultNode = xml.selectSingleNode("/trx:TestRun/trx:ResultSummary");
+            if (resultNode === null) {
+                throw { message: "Unable to find results summary" };
+            }
+
+            var outcome = resultNode.getAttribute("outcome");
+            if (outcome !== "Passed") {
+                WScript.Echo("Outcome: " + outcome);
+                var failedTests = xml.selectNodes("/trx:TestRun/trx:Results/trx:UnitTestResult[@outcome != 'Passed']/@testName");
+                for (var i = 0; i < failedTests.length; i++) {
+                    WScript.Echo("  Failed test: " + failedTests[i].value);
+                }
+                exitCode = 2;
+            } else {
+                WScript.Echo("All tests passed.");
+            }
+        }
+
+        function CheckUrl(url) {
+            var xhr = WScript.CreateObject("Msxml2.ServerXMLHTTP.6.0");
+            xhr.open("GET", url, false);
+            var success = false;
+            try {
+                xhr.send();
+                success = (xhr.status === 200);
+                if (!success) {
+                    WScript.Echo("status: " + xhr.status + " - " + xhr.statusText);
+                }
+            } catch (err) {
+                WScript.Echo("error: " + err.message);
+            }
+
+            return success;
+        }
+
+        function ExpandWildcard(path) {
+            var wcRegEx = /\\\*\*?\\/;
+            var wcMatch = wcRegEx.exec(path);
+
+            var paths = [];
+            if (wcMatch !== null) {
+                var recursive = wcMatch[0] === "\\**\\";
+                var basePath = path.substring(0, wcMatch.index);
+                var relativePath = path.substring(wcMatch.index + wcMatch[0].length);
+
+                if (fso.FolderExists(basePath)) {
+                    var folder = fso.GetFolder(basePath);
+                    var subFolders = new Enumerator(folder.SubFolders);
+
+                    paths = paths.concat(ExpandWildcard(basePath + "\\" + relativePath));
+
+                    for (; !subFolders.atEnd(); subFolders.moveNext()) {
+                        var expandedPath = subFolders.item().Path + "\\"
+                        if (recursive) {
+                            expandedPath += "**\\";
+                        }
+                        expandedPath += path.substring(wcMatch.index + wcMatch[0].length);
+                        paths = paths.concat(ExpandWildcard(expandedPath));
+                    }
+                }
+            } else {
+                paths.push(path);
+            }
+            return paths;
+        }
+
+        function FindFirstPath(candidates) {
+            /// <summary>Finds the first path present from a candidate list.</summary>
+            /// <param name="candidates" type="Array">Array of paths (possibly with environment variables).</param>
+            /// <returns type="String">The first folder on disk found; null if none are present.</returns>
+
+            var paths = [];
+
+            for (var i = 0; i < candidates.length; i++) {
+                var path = shell.ExpandEnvironmentStrings(candidates[i]);
+                paths = paths.concat(ExpandWildcard(path));
+            }
+
+            for (var i = 0; i < paths.length; i++) {
+                if (fso.FolderExists(paths[i]) || fso.FileExists(paths[i])) {
+                    return paths[i];
+                }
+            }
+            return null;
+        }
+
+        function RunCommand(command, waitForExit, expectedExitCode) {
+            /// <summary>Runs a command or program</summary>
+            /// <param name="command" type="String">Command to run</param>
+            /// <param name="waitForExit" type="Boolean">Whether to wait for program to exit</param>
+            /// <param name="expectedExitCode" type="Integer">If waitForExit is true, throw if the exit code is not expected</param>
+            /// <returns type="Integer">The exitcode if waitForExit is true; always 0 if waitForExit is false</returns>
+            WScript.Echo("[cmd] " + command);
+            var exitCode = shell.Run(command, 0, waitForExit);
+            if (expectedExitCode !== undefined && exitCode !== expectedExitCode) {
+                throw { message: "Process exited with unexpected exit code. (Expected: " + expectedExitCode + ", Actual: " + exitCode + ")" };
+            } else {
+                return exitCode;
+            }
+        }
+
+        function SetupWebDevServer() {
+            /// <summary>Starts up IIS Express if it's not running.</summary>
+            /// <returns type="String">The URL to the server root.</returns>
+            var siteName = "DataJS Development Site";
+            var appName = "datajs";
+            var port = "8989";
+            var result = "http://" + shell.ExpandEnvironmentStrings("%COMPUTERNAME%").toLowerCase() + ":" + port + "/" + appName + "/";
+            var url = result + "tests/common/TestLogger.svc";
+
+            var success = CheckUrl(url);
+
+            if (!success) {
+                // Assume that we need to launch this.
+                var src = fso.GetAbsolutePathName("..");
+
+                var folder = FindFirstPath([
+                    "%ProgramFiles(x86)%\\IIS Express",
+                    "%ProgramFiles%\\IIS Express"]);    
+
+                if (!folder) {
+                    throw { message: "Unable to find path to IIS Express" };
+                }
+
+                var appCmd = "\"" + folder + "\\appcmd.exe\"";
+                var iisExpress = "\"" + folder + "\\iisexpress.exe\"";
+
+                // Delete site if it already exists
+                WScript.Echo("Checking if site '" + siteName + "' already exists...");
+                if (RunCommand(appCmd + " list site \"" + siteName + "\"", true) === 0) {
+                    WScript.Echo("Deleting existing site '" + siteName + "'...");
+                    RunCommand(appCmd + " delete site \"" + siteName + "\"", true, 0);
+                }
+
+                // Create site and app
+                WScript.Echo("Creating site '" + siteName + "'...");
+                RunCommand(appCmd + " add site /name:\"" + siteName + "\" /bindings:http/*:" + port + ": /physicalPath:%IIS_BIN%\\AppServer\\empty_wwwroot", true, 0);
+
+                WScript.Echo("Creating application '" + appName + "'...");
+                RunCommand(appCmd + " add app /site.name:\"" + siteName + "\" /path:\"/" + appName + "\" /physicalPath:\"" + src + "\"", true, 0);
+
+                // Start the server
+                WScript.Echo("Starting IIS Express server...");
+                RunCommand(iisExpress + " /site:\"" + siteName + "\" /trace:error");
+
+                WScript.Sleep(2 * 1000);
+                success = attempt(function () {
+                    WScript.Echo("Waiting for server to come up, looking for " + url + " ...");
+                    return CheckUrl(url);
+                }, 5 * 1000, 3);
+
+                if (!success) {
+                    throw { message: "Unable to verify the URL at " + url };
+                }
+            }
+            return result;
+        }
+
+        function CreateTestRunId(serviceRoot) {
+            /// <summary>Creates a new test run ID from the service.</summary>
+            /// <param name="serviceRoot" type="String">Root of logger service.</param>
+            /// <returns type="String">The test run ID created.</returns>
+            var xhr = WScript.CreateObject("Msxml2.ServerXMLHTTP.6.0");
+            var url = serviceRoot + "CreateTestRun";
+            xhr.open("GET", url, false);
+            WScript.Echo("URL: " + url);
+            xhr.send();
+
+            var response = xhr.responseText;
+            var result = parseJson(response);
+            return result;
+        }
+
+        function GetBrowsers() {
+            /// <summary>Gets the browsers that should be used for running the tests.</summary>
+            /// <returns type="Object">Dictionary object containing the browser and its executable path as key value pairs.</returns>
+            var localAppData = fso.FolderExists(shell.ExpandEnvironmentStrings("%LOCALAPPDATA%")) ? "%LOCALAPPDATA%" : "%USERPROFILE%\\Local Settings\\Application Data";
+            var programFiles = fso.FolderExists(shell.ExpandEnvironmentStrings("%ProgramFiles(x86)%")) ? "%ProgramFiles(x86)%" : "%ProgramFiles%";
+            var browsers = {
+                IE8: [programFiles + "\\Internet Explorer\\iexplore.exe"],
+                Firefox4: [programFiles + "\\Mozilla Firefox\\firefox.exe"],
+                Chrome: [programFiles + "\\Google\\Chrome\\Application\\chrome.exe", localAppData + "\\Google\\Chrome\\Application\\chrome.exe"],
+                Safari5: [programFiles + "\\Safari\\safari.exe"],
+                Opera: [programFiles + "\\Opera\\opera.exe"]
+            };
+
+            var browsersToRun = {};
+
+            if (WScript.Arguments.Named.Exists("browsers")) {
+                browserNames = WScript.Arguments.Named.Item("browsers").split(',');
+                for (i in browserNames) {
+                    var browserName = browserNames[i];
+                    if (browsers[browserName]) {
+                        browsersToRun[browserName] = browsers[browserName];
+                    } else {
+                        throw { message: "Unknown browser: " + browserName };
+                    }
+                }
+            }
+            else {
+                browsersToRun = browsers;
+            }
+
+            return browsersToRun;
+        }
+
+        function GetTestFilesList() {
+            /// <summary>Gets the list of test files that are going to be executed in the test run.</summary>
+            /// <returns type="Array">The list of test files.</returns>
+            var testFilesList = null;
+            if (WScript.Arguments.Named.Exists("testFiles")) {
+                testFilesList = WScript.Arguments.Named.Item("testFiles").split(',');
+            }
+
+            if (testFilesList === null) {
+                testFilesList = getAllTestFiles();
+            }
+
+            WScript.Echo("Test files to be executed: " + testFilesList.toString());
+            return testFilesList;
+        }
+
+        function GetOutputDirectory() {
+            /// <summary>Gets the test run output directory.</summary>
+            /// <returns type="String">Output directory.</returns>
+            var result;
+            if (WScript.Arguments.Named.Exists("outputDirectory")) {
+                result = WScript.Arguments.Named.Item("outputDirectory");
+            } else {
+                result = shell.ExpandEnvironmentStrings("%DJSOUT%\\JSLib.sln\\tests");
+            }
+
+
+            return result;
+        }
+
+        try {
+            var root = SetupWebDevServer();
+            var serviceRoot = root + "tests/common/TestLogger.svc/";
+            var testRunId = CreateTestRunId(serviceRoot);
+            WScript.Echo("Test Run ID: " + testRunId);
+
+            var testFilesList = GetTestFilesList();
+            var browsers = GetBrowsers();
+            var outputDirectory = GetOutputDirectory();
+
+            if (testFilesList.length > 0) {
+                var url = root + "tests/" + testFilesList[0] + "?testRunId=" + testRunId;
+                LaunchBrowser(browsers, serviceRoot, testFilesList.splice(1, testFilesList.length), url, testRunId, outputDirectory);
+                WriteTestRunResults(serviceRoot, testRunId, outputDirectory);
+            }
+            else {
+                WScript.Echo("No test files specified to run.");
+            }
+        } catch (e) {
+            WScript.Echo("Error running tests");
+            for (var p in e) WScript.Echo(p + ": " + e[p]);
+            exitCode = 1;
+        }
+
+        WScript.Quit(exitCode);
+
+    </script>
+</job>
\ No newline at end of file
diff --git a/JSLib/tests/test-list.js b/JSLib/tests/test-list.js
new file mode 100644
index 0000000..3a805d1
--- /dev/null
+++ b/JSLib/tests/test-list.js
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// Test list for datajs tests
+
+function getAllTestFiles() {
+    return [
+        "odata-qunit-tests.htm"
+    ];
+}
\ No newline at end of file