How to read attribute value from xml file - FAKE F#MAKE - f#

Hi I am having an XML file , I want to read value of a particular attribute from XML file , how will I do this in FAKE . Please Help , I am new to F# and FAKE.And I just figured it out on fake documentation page and found that I can use XMLRead function of XMLHelper Class whose description is available at
https://github.com/fsharp/FAKE/blob/master/src/app/FakeLib/XMLHelper.fs#L14-14
But I could not understand how to do this , as there are no examples as such .
I have a huge XML file but for simplicity I am mentioning a piece of that file. XML File is as follows :
<version>
<major number="2">
<minor>1</minor>
<build>1</build>
<revised>1</revised>
</major>
</version>
Please tell me , how to read values from mentioned attributes .

If you look back a few posts you can find a related question.
The short answer is that in my 'build.fsx' file I can do somthing like this:
// Build the main module with MSBuild
Target "BuildMain" (fun _ ->
for s in XMLHelper.XMLRead true "./myxml.xml" "" "" "/version/major/minor"
do trace s
!! "./Kapoin_03_Main/Kapoin_03_Main.fsproj"
|> MSBuild buildDir "Build" buildProps
|> Log "Main build output: " )
That will in this case just write "1" in the console when you build.
Edit: If you are using a different target you may want to do something along the lines of the following to store a value as a variable:
let minver =
XMLHelper.XMLRead
true "./myxml.xml" "" "" "/version/major/minor"
|> Seq.head

Related

CopyRecursive not working in FAKE script

I have the following target defined in my FAKE build script. It is being executed. I have verified this usuing trace statements. There is a large file structure in the folder at "MyWebApp". I get no output at the destination folder. What could I be doing wrong?
Target "Push" (fun _ ->
let dir= FileSystemHelper.currentDirectory
let src = dir+ #"\deploy\" + version + #"\MyWebApp"
let dest = #"c:\windows\temp\deploy\" + version
CopyRecursive src dest |> ignore
()
Thanks
Jim
If you look at the definition of CopyRecursive
let CopyRecursive dir outputDir = copyRecursive (directoryInfo dir) (directoryInfo outputDir)
you may notice it represents the partially evaluated (and, by the way, marked at the moment as obsolete) function copyRecursive having signature bool->string list.
Your script sends this partially evaluated function value to ignore and, apparently, nothing happens as the result.
In order to allow copyRecursive to do its work just provide the missing third input argument of type bool that defines if the function should override files with the same names in the target directory. Assuming this is the behavior that you want just change the correspondent line in your script by
CopyRecursive src dest true |> ignore
This will allow copyRecursive to perform its side-effect copying magic returning the list of names of copied files, that you may discard with ignore.

Can I create a conditional literal?

In order to create a Json provider I need to pass a literal with the path. There are several people working on the project from different locations, and the paths are different in each case. (Actually only the beginning of each path). I tried to create a literal with pattern matching but the compiler does not accept it. Is there another way to do this?
My failed attempt is below:
open FSharp.Data
[<Literal>]
let bitbucketRoot = // Error message: This is not a valid constant expression
let computerName = Environment.MachineName
match computerName with
| "DESKTOP-G3OF32U" -> "C:\\Users\\Fernando"
| "HPW8" -> #"H:\Dropbox\"
| _ -> failwith "Unknown computer"
[<Literal>] // Error message: This is not a valid constant expression
let projDataPath = bitbucketRoot + #"Bitbucket\VSProjects\Fractal10\Fractal10\data\"
[<Literal>] // Error message: This is not a valid constant expression
let jsonPath = projDataPath + "fractal.json"
type PathInfo = JsonProvider<Sample=jsonPath>
I would advise that you store it in source control and make it a path relative to your project root, assuming you are working out of a common source control repository.
Either that, or host the sample on a public URL. (I wouldn't actually recommend this because including it in your source repository allows versioning and doesn't publicly expose your data)
You cannot create a conditional literal as the other comments point it out. However this is a fairly frequent use case and the way to deal with it is as follows:
#r #"..\packages\FSharp.Data\lib\net40\FSharp.Data.dll"
open FSharp.Data
open System
open System.IO
[<Literal>]
let JsonSource = __SOURCE_DIRECTORY__ + #"\test.json"
type JSonType = JsonProvider<JsonSource>
let json1 = JSonType.GetSamples()
let anotherPath = #"C:\tmp"
let anotherJson = anotherPath + #"\test.json"
let json2 = JSonType.Load(anotherJson)
The __SOURCE_DIRECTORY__ directive will point to the project root (just display it in the REPL) and then you can add the filename to it and make that a literal. If you check in this file into a git repo, then everyone who checks it out can have it in a relative path, and you can refer it when generating the type. When actually using the type or referring to the full file you can just use the .Load() method to load any file, and this doesn't have to be a literal.
There is actually a second way, which could work for you depending on the circumstances, compile a sample, and distribute it as a .dll. You can refer to this and use it directly without having access to the actual file. Please see the Using the JSON Provider in a Library section at the end of the documentation.
I have not tried referring to the json in a config file, it might also be possible.

Manipulate an XML file with FAKE

Question: How do I set a specific attribute in an XML file with FAKE?
I want to build a Windows Phone 8.1 project with the help of FAKE. The version tag is a parameter of the build script. The tag is already baked into the assemply info:
let version = getBuildParamOrDefault "version" "0.0.0.1"
Target "AssemblyInfo" (fun _ ->
CreateCSharpAssemblyInfo "./src/SharedAssemblyInfo.cs"
[
Attribute.Product product
Attribute.Version version
Attribute.FileVersion version
Attribute.Metadata ("githash", commitHash)
]
)
There is another file in a Windows Phone 8.1 project that contains version information: Package.appxmanifest.
<?xml version="1.0" encoding="utf-8"?>
<Package ...>
<Identity Name="..." Publisher="..." Version="1.0.0.0" />
...
</Package>
I want to change the value of the Version attribute of the Identity tag. It should contain the version tag given as build parameter. I want to do it in a separate target with the name "AppxManifest" and it should execute after manipulating the assembly info file but before the MSBuild build is executed:
Target "AppxManifest" (fun _ ->
???
)
"Clean"
==> "RestorePackages"
==> "AssemblyInfo"
==> "AppxManifest"
==> "Build"
I have found an XMLHelper in the documentation of FAKE (http://fsharp.github.io/FAKE/apidocs/fake-xmlhelper.html) and it looks like that this thing can do the job. But there are no examples. I could not figure out how to do it.
You can use the XMLHelper library to manipulate XML files. The function XmlPoke can be used to change an element in an XML file by referenceing it with an XPath expression. The Package.appxmanifest file contains namespaces, therefore you have to take the sister function XmlPokeNS which can handle namespaces properly.
The last difficulty is the default namespace in Package.appxmanifest. You have to specify a prefix which you associate with the default namespace. In your XPath expression this prefix is used to reference the XML elements.
There is no special prefix string, you can choose whatever you want. In this example I choose the prefix "df":
open Fake.XMLHelper
Target "PackageAppxmanifest" <| fun _ ->
let file = "<path to Package.appxmanifest>"
let ns = [ "df", "http://schemas.microsoft.com/appx/2010/manifest" ]
let xpath = #"df:Package/df:Identity/#Version"
XmlPokeNS file ns xpath version

Sharing const variables across FAKE fsx scripts

Is there any way to share a variable by including a fsx script within another fsx script.
e.g script buildConsts.fsx contains
let buildDir = "./build/"
I want to reference this in other build scripts e.g.
#load #".\buildConsts.fsx"
let testDlls = !! (buildDir + "*Test*.dll")
When I attempt to run the script the 'buildDir' variable the script fails to compile.
This is a fairly common approach that is used with tools such as MSBuild and PSAKE to modularise scripts. Is this the correct approach with FAKE ?
What you're doing should work - what exactly is the error message that you're getting?
I suspect that the problem is that F# automatically puts the contents of a file in a module and you need to open the module before you can access the constants. The module is named based on the file name, so in your case buildConsts.fsx will generate a module named BuildConsts. You should be able to use it as follows:
#load #".\buildConsts.fsx"
open BuildConsts
let testDlls = !! (buildDir + "*Test*.dll")
You can also add an explicit module declaration to buildconsts.fsx, which is probably a better idea as it is less fragile (won't change when you rename the file):
moule BuildConstants
let buildDir = "./build/"

control file structure of a zip archive in FSharp

the following is a code sample that takes a list of file names and zips them into a single archive. The problem I'm having is that I'd like for the file described by filname be in the top level of the zip archive (i.e. when the archive is opened, "clientName....xml" is the first thing you see, instead of the folder "XML").
let filename = sprintf "C:\\XML\\ClientName_%s.xml" (System.DateTime.Now.ToString("ddMMyyyy"))
use fs = new FileStream(filename, FileMode.Create)
let xmlSerializer = XmlSerializer(typeof<log>)
xmlSerializer.Serialize(fs,logObj)
fs.Close()
use zipfile = new ZipFile()
let basePath = path.Replace("/", "\\")
for fileObj in files do
let relativeFilePath = basePath + (fileObj.Filename).Replace("/", "\\")
printfn "%s" relativeFilePath
zipfile.AddFile(relativeFilePath) |> ignore
()
zipfile.AddFile(filename) |> ignore
let zipFileName = sprintf "C:\\XML\\Compliance_%s.zip" (System.DateTime.Now.ToString("ddMMyyyy"))
zipfile.Save(zipFileName)
Where does the ZipFile type come from? I don't think this is a standard .NET class... I tried searching and found this library http://dotnetzip.codeplex.com/ which has a class matching to your sample :-)
The mentioned library also has AddFile overload that takes two string - the source file name and a relative file name in the ZIP file. This seems exactly like what you're looking for. I guess the call would be something like zipfile.AddFile(absolutePath, "/")...

Resources