lundi 19 janvier 2015

SSRS: All report in one page in the browser

In Sql Server Reporting Service, if you want to get your report without pagination you have to disable soft page break.
This can be done by setting InteractiveHeight to 0.
  1. Select the report property by clicking anywhere outside the report.
  2. Set the InteractiveSize - Height to 0

lundi 12 janvier 2015

Je suis Charlie

while ( true ) {
    Console.WriteLine("Je suis Charlie");
}

jeudi 1 janvier 2015

Merging runs in an openxml file/document

The goal is to reduce the number of runs in the paragraphs of a document. I made the choice not to use the openxml SDK. Indeed my goal is a templating engine. In my process the runs may contain xsl, that is xml. So I made the choice to stay on raw XML tools.

The operational (without the usings, except those revealing a dependency) code looks like:

namespace SandBox {
    class Program {
        static void Main(string[] args) {
            try {

                String fileName = @"somepath\somefile.docx";
                String destFile = "res.docx";
                
                Tuple<XPathNavigator, XmlNamespaceManager> xp = 
                    ZDocx.GetNavigatorAndManagerFromString(
                        ZDocx.GetDocxDocumentStringFromDocxFile(fileName));
                XPathNavigator xpn = xp.Item1;
                XmlNamespaceManager xnm = xp.Item2;

                XPathNodeIterator xni = xpn.Select("//w:p", xnm);
                while (xni.MoveNext()) {
                    //Merge all runs ignoring styles
                    //ZDocx.MergeRuns(xni.Current);
                    //Merge considering only Bold as a grouping condition
                    ZDocx.MergeRuns(xni.Current, new ByStylesNodesComparator  {
                        Settings = new ByStylesNodesComparatorSettings {
                            CheckBold = true
                    }});                    
                }

                System.IO.File.Copy(fileName, destFile, true);
                ZDocx.SetDocxDocumentStringToDocxFile(xpn, destFile);

            } catch (Exception ex) {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

There are 3 main steps:

  • I fristly load the document.xml part the openxml file in an XPathNavigator
  • Then I process each paragraph.
  • Finally I inject the modified document.xml in a new openxml file.

First the cooking code:

using Ionic.Zip;

namespace SandBox {
    public class ZDocx {
        public static String nsW = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";

        public static String GetDocxDocumentStringFromDocxFile(String fileName) {
            String tf = System.IO.Path.GetTempFileName();
            StreamWriter sw = new StreamWriter(tf);
            using (ZipFile zip = ZipFile.Read(fileName)) {
                ZipEntry e = zip["word\\document.xml"];
                e.Extract(sw.BaseStream);                
            }
            try {
                sw.Close();
            } catch { }
            
            StreamReader sr = new StreamReader(tf);
            String st = sr.ReadToEnd();
            sr.Close();
            System.IO.File.Delete(tf);
            
            return st;
        }

        public static void SetDocxDocumentStringToDocxFile(XPathNavigator xdoc, String fileName) {            
            using (ZipFile zip = ZipFile.Read(fileName)) {
                using (MemoryStream ms = new MemoryStream()) {
                    XmlWriterSettings xws = new XmlWriterSettings {
                        Encoding = Encoding.UTF8
                    };
                    using (XmlWriter xw = XmlWriter.Create(ms, xws)) {
                        xw.WriteNode(xdoc, false);
                        xw.Flush();
                        ms.Position = 0;
                        zip.UpdateEntry("word\\document.xml", ms);
                        zip.Save();
                    }
                }
            }            
        }

        public static Tuple<XPathNavigator, XmlNamespaceManager> GetNavigatorAndManagerFromString(String st) {
            XmlDocument xml = new XmlDocument();
            StringReader stReader = new StringReader(st);
            XmlReader xReader = XmlReader.Create(stReader, new XmlReaderSettings() { 
                IgnoreWhitespace = false, 
                CloseInput = true });
            xml.Load(xReader);
            xReader.Close();
            XPathNavigator navXml = xml.CreateNavigator();
            XmlNamespaceManager manager = new XmlNamespaceManager(navXml.NameTable);
            manager.AddNamespace("w", nsW);
            manager.AddNamespace("xsl", "http://www.w3.org/1999/XSL/Transform");

            return new Tuple<XPathNavigator, XmlNamespaceManager>(navXml, manager);
        }

    }
}

Then the merging code:

namespace SandBox {
    public class ZDocx {
        public static void MergeRuns(XPathNavigator paragraph, INodeComparator areMergeable = null) {
            if (paragraph.LocalName != "p")
                throw new Exception("MergeRuns: paragraph is not a 'w:p'.");
            if (areMergeable == null)
                areMergeable = AlwaysTrueNodesComparator.GetInstance();

            XPathNavigator destRun = null;
            XPathNavigator lPara = paragraph.Clone();
            lPara.MoveToFirstChild();
            do {
                if (lPara.LocalName != "r")
                    continue;

                if (destRun == null) {
                    destRun = lPara.Clone();
                    continue;
                }

                if ( areMergeable.AreMergeable(destRun, lPara) ) {
                    XPathNavigator nL = lPara.Clone();
                    XPathNavigator nK = destRun.Clone();
                    nL.MoveToChild("t", nsW);
                    nK.MoveToChild("t", nsW);
                    nK.InnerXml += nL.InnerXml;                    
                    nL.MoveToParent();
                    nL.MoveToPrevious();
                    lPara.DeleteSelf();
                    lPara = nL;
                } else {
                    destRun = lPara.Clone();
                }

            } while (lPara.MoveToNext());
        }
    }
}
In the previous code, one key is the areMergeable parameter: this parameter allows to decide how the merging occurs. This parameter implements the following interface.
    public interface INodeComparator {
        Boolean AreMergeable(XPathNavigator xpn1, XPathNavigator xpn2);
    }
This interface may be implemented as in the following samples provided as an inspiration root:
namespace SandBox {
    public class AlwaysTrueNodesComparator : INodeComparator {
        private AlwaysTrueNodesComparator() {}

        private static AlwaysTrueNodesComparator _inst = new AlwaysTrueNodesComparator();
        public static AlwaysTrueNodesComparator GetInstance() {
            return _inst;
        }

        public Boolean AreMergeable(XPathNavigator xpn1, XPathNavigator xpn2) {
            return true;
        }
    }

    public class ByStylesNodesComparatorSettings {
        public Boolean CheckBold { get; set; }
        public Boolean CheckUnderlined { get; set; }
        public Boolean CheckItalic { get; set; }
        public Boolean CheckStriked { get; set; }
    }

    public class ByStylesNodesComparator : INodeComparator {
        public ByStylesNodesComparatorSettings Settings { get; set; }

        public Boolean AreMergeable(XPathNavigator xpn1, XPathNavigator xpn2) {
            XPathNavigator nav1 = xpn1.Clone();
            XPathNavigator nav2 = xpn2.Clone();

            Boolean nav1HasRPr = nav1.MoveToChild("rPr", ZDocx.nsW);
            Boolean nav2HasRPr = nav2.MoveToChild("rPr", ZDocx.nsW);

            Boolean b1, b2;

            if (Settings == null || Settings.CheckBold) {
                b1 = nav1HasRPr && nav1.SelectChildren("b", ZDocx.nsW).Count == 1;
                b2 = nav2HasRPr && nav2.SelectChildren("b", ZDocx.nsW).Count == 1;
                if (b1 != b2)
                    return false;
            }

            if (Settings == null || Settings.CheckUnderlined) {
                b1 = nav1HasRPr && nav1.SelectChildren("u", ZDocx.nsW).Count == 1;
                b2 = nav2HasRPr && nav2.SelectChildren("u", ZDocx.nsW).Count == 1;
                if (b1 != b2)
                    return false;
            }

            if (Settings == null || Settings.CheckItalic) {
                b1 = nav1HasRPr && nav1.SelectChildren("i", ZDocx.nsW).Count == 1;
                b2 = nav2HasRPr && nav2.SelectChildren("i", ZDocx.nsW).Count == 1;
                if (b1 != b2)
                    return false;
            }

            if (Settings == null || Settings.CheckStriked) {
                b1 = nav1HasRPr && nav1.SelectChildren("strike", ZDocx.nsW).Count == 1;
                b2 = nav2HasRPr && nav2.SelectChildren("strike", ZDocx.nsW).Count == 1;
                if (b1 != b2)
                    return false;
            }

            return true;
        }
    }
}

Take care to clone the navigators in the AreMergeable method to not surprise the caller.

Using AlwaysTrueNodesComparator reduces all paragraphs to one single run with the styles (or not) of the first run of the reduced paragraph.

Using ByStylesNodesComparator allows to merge runs according to some part of their styles. In the implemented class the handled style are Bold, Underline, Strike and Italic. Be careful that only basis underlining is handled. The underline style is not handled.

samedi 8 novembre 2014

Templating from Object

The idea here is to provide a really lightweight template engine.
The point here is to demonstrate how light such a code can be. In his current state the code suffers some defaults discussed at the end.
A template is a String comprising tokens of the form [ProperyName] or [ProperyName:format]. PropertyName can be any name of any property of an object or navigable property of the object. However IEnumerable are not handled.
public static String MergeObjectFromTemplate(String st, Object o) {
    if (String.IsNullOrEmpty(st)) throw new ArgumentNullException("st");
    if (o == null) return st;

    Int32 i = st.IndexOf("[");
    Int32 j = 0;
    String pName;
    Object lo;
    String[] a;
    while (i >= 0) {
        j = st.IndexOf("]");
        if (j == -1)
            return st;

        pName = st.Substring(i + 1, j - i - 1);
        a = pName.Split(':');
        lo = GetObjectByReflection(o, a[0]);
        if (lo == null) { 
            st = st.Replace("[" + pName + "]", "_" + pName + "_");
        } else {
            if (a.Count() == 1) {
                st = st.Replace("[" + pName + "]", lo.ToString().Trim());
            } else {
                st = st.Replace("[" + pName + "]", String.Format("{0:" + a[1] + "}", lo));
            }
        }

        i = st.IndexOf("[");
    }

    return st;
}
The key function here is: GetObjectByReflection.
public static Object GetObjectByReflection(Object src, String propertyName) {
    Type type;
    Object o = src;
    foreach (String fragment in propertyName.Split('.')) {
        type = o.GetType();
        PropertyInfo pi = type.GetProperty(fragment);
        if (pi == null) {
            o = null;
            break;
        }
        o = pi.GetValue(o, null);
        if (o == null) return String.Empty;
    }

    return o;
}
That is it!
Well, not totally. It remains some defaults, but for 50 lines, that's may be not so bad. Known defaults are:

  • Bad format string are not handled.
  • This is blocking, if throwing an exception is blocking. Correcting it is easy once expected behavior is known. At the time the code was updated I was just needing to format DateTime in a relatively safe environment: templates are edited by me. It is another story if you want to allow user to produce templates.
  • Excepted for a bad format String, the code always returns a String, even if a bad object is provided, or bad property name is used.

Windows 8 new account

I recently try to create a new account on a relatively old windows 8 computer (one year, several updates).

Account creation was just fine but... impossible to connect due to the inability of the system to load the profile of the newly created user.

After some googling and hours, I finally found that the problem was the inability of the system to create, by copying from c:\Users\Default, the new profile.

I found which files were involved with the windows events observer:


Once the rights fixed, I was able to use the new account.


mercredi 29 octobre 2014

Setup Apache 2.2 for FastCgi handling of PHP on windows 7

Why ?
Because I used to setup PHP as an Apache Module. But, this morning, after starting my good old 2012 apache2.2 service, it says "mysql driver not installed"! I'm quite sure they were in 2012. In addition, IIS, on the same php.ini, says they are installed. So I try Apache with FastCGI as IIS does. Then, my drivers come back.
We here assume that
  • Apache 2.2 is yet installed.
  • php5 is in a directory named c:\php-5
From this assumptions:
Edit httpd.conf as follow:
    #LoadModule php5_module "c:/php-5/php5apache2_2.dll"
    #AddHandler application/x-httpd-php .php
    #PHPIniDir "C:/php-5"

    LoadModule fcgid_module modules/mod_fcgid.so
    FcgidInitialEnv PHPRC "c:/php-5"
    AddHandler fcgid-script .php
    FcgidWrapper "c:/php-5/php-cgi.exe" .php
And, to avoid HTTP 403 errors, don't forget ExecCGI in your directory configuration.
    
        Options Indexes FollowSymLinks ExecCGI
        #...
    
By the way, mod_fcgid.so can be found here (http://mir2.ovh.net/ftp.apache.org/dist//httpd/binaries/win32/)
I'm on a windows 7 64bits professional edition.

lundi 22 septembre 2014

Managing default values for entities in EF II: the just on time way

You probably still know how to create an entity with default values. This is the 'from birth' way.
Well, there is another way to set default values: a 'just on time way'.
The principle is to override the SaveChanges method to explore entities in the tracker to find the one having unset values, just like in the 'from birth way'. Here it is how it can be done.
        //the default value for datetime properties
        public DateTime DefaultDate { get; set; }

        public override int SaveChanges() {
            Boolean defaultSet = false;

            //seeking only pertinent entities
            foreach (DbEntityEntry dbee in ChangeTracker.Entries().
                     Where(x => x.State != EntityState.Deleted && x.State != EntityState.Detached ) ) {
                defaultSet = false;
                Object o = dbee.Entity;
                Type t = o.GetType();
                foreach ( MemberInfo m in t.GetProperties() ) {
                    PropertyInfo p = t.GetProperty(m.Name);
                    switch (Type.GetTypeCode(p.PropertyType)) {
                        case TypeCode.String:
                            if (p.GetValue(o, null) == null) {
                                p.SetValue(o, String.Empty, null);
                                defaultSet = true;
                            }
                            break;
                        case TypeCode.DateTime:
                            if ((DateTime)p.GetValue(o, null) == DateTime.MinValue) {
                                p.SetValue(o, DefaultDate, null);
                                defaultSet = true;
                            }
                            break;
                    }
                    
                    //update the state if needed
                    if ( defaultSet ) {
                        switch ( dbee.State ) {
                            case EntityState.Unchanged :
                                dbee.State = EntityState.Modified;
                                break;
                        }
                    }
                }
            }

            return base.SaveChanges();
        }
This could be used to implement business rules, such as LastModificationDate.
Before EF6, you may have to look for the tracker in the underlying ObjectContext
A full piece of code:
using System;
using System.Linq;
using System.Data.Entity;
using System.Collections.Generic;
using System.Data.Entity.ModelConfiguration;
using System.Reflection;
using System.Data.Entity.Infrastructure;

namespace testef {
    public class Order {
        public Int32 Id { get; set; }
        public String Title { get; set; }

        public virtual ICollection Details { get; set; }

        public DateTime OrderDate { get; set; }

        public virtual OrderType Type { get; set; }
    }

    public class OrderType {
        public String Code { get; set; }
        public String Description { get; set; }
    }

    public class OrderDetail {
        public virtual Order Order { get; set; }
        public Int32 Id { get; set; }
        public String D { get; set; }
        public Boolean IsActive { get; set; }
    }

    public class OrderDetailConfiguration : EntityTypeConfiguration {
        public OrderDetailConfiguration()
            : base() {
            HasRequired(d => d.Order).WithMany(o => o.Details);
        }
    }

    public class OrderConfiguration : EntityTypeConfiguration {
        public OrderConfiguration()
            : base() {
            HasRequired(d => d.Type).WithMany();
        }
    }

    public class OrderTypeConfiguration : EntityTypeConfiguration {
        public OrderTypeConfiguration()
            : base() {
            Property(x => x.Code).HasColumnType("varchar").HasMaxLength(10);
            HasKey(x => x.Code);
        }
    }

    public class TestEFContext : DbContext {
        public DbSet Orders { get; set; }
        public DbSet Details { get; set; }

        public TestEFContext(String cs)
            : base(cs) {
            Database.SetInitializer(new DropCreateDatabaseAlways());
            //Database.SetInitializer(null);
            //Database.SetInitializer(new CreateDatabaseIfNotExists());
            //Database.SetInitializer(new CustomDataBaseInitializer());

        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder) {
        }

        public DateTime DefaultDate { get; set; }

        public override int SaveChanges() {

            Boolean defaultSet = false;
            foreach (DbEntityEntry dbee in ChangeTracker.Entries().
                     Where(x => x.State != EntityState.Deleted && x.State != EntityState.Detached ) ) {
                defaultSet = false;
                Object o = dbee.Entity;
                Type t = o.GetType();
                foreach ( MemberInfo m in t.GetProperties() ) {
                    PropertyInfo p = t.GetProperty(m.Name);
                    switch (Type.GetTypeCode(p.PropertyType)) {
                        case TypeCode.String:
                            if (p.GetValue(o, null) == null) {
                                p.SetValue(o, String.Empty, null);
                                defaultSet = true;
                            }
                            break;
                        case TypeCode.DateTime:
                            if ((DateTime)p.GetValue(o, null) == DateTime.MinValue) {
                                p.SetValue(o, DefaultDate, null);
                                defaultSet = true;
                            }
                            break;
                    }
                    if ( defaultSet ) {
                        switch ( dbee.State ) {
                            case EntityState.Unchanged :
                                dbee.State = EntityState.Modified;
                                break;
                        }
                    }
                }
            }

            return base.SaveChanges();
        }
    }

    public class CustomDataBaseInitializer : CreateDatabaseIfNotExists {
        public CustomDataBaseInitializer()
            : base() {
        }
    }
        
    class Program {
        static void Main(string[] args) {
            String cs = @"Data Source=ALIASTVALK;Initial Catalog=TestEF;Integrated Security=True; MultipleActiveResultSets=True";
            using (TestEFContext ctx = new TestEFContext(cs)) {
                ctx.DefaultDate = new DateTime(2000, 1, 1);

                OrderType t = new OrderType { 
                    Code = "T1",
                    Description = "type One"
                };
                ctx.Orders.Add(new Order { 
                    Title = "first order",
                    Type = t
                });

                ctx.SaveChanges();
            }

            using (TestEFContext ctx = new TestEFContext(cs)) {
                Order o = ctx.Orders.First();
                Console.WriteLine(o.OrderDate);
            }
        }
    }
}