Local Reports - Globalizace a Lokalizace

Jan Holan       10. 1. 2014       ASP.NET WebForms, Tisk       5541 zobrazení

Dnes volné doplnění k mému předchozímu článku o tvorbě dokumentů pomoci nástroje Local Reports. Koukneme se jak v Local Reports ošetřit globalizaci a jak řešit jazykové mutace reportu.

Globalizace

Když v reportu budete zobrazovat pole například typu DateTime, zjistíte, že bude datum formátovaný v anglické kultuře, a to i když jej voláte na české (nebo jiné) kultuře. V pojetí .NET se bavíme o CurrentCulture. A právě tato kultura je v reportu k dispozici jako hodnota výrazu User!Language. Aby všechny pole reportu tuto hodnotu převzali, musíme nastavit vlastnost Language reportu právě na tuto hodnotu:

=User!Language

image

Nyní jsou již pole zobrazována ve správné kultuře.

Lokalizace

O něco složitější to budeme mít s otázkou lokalizace reportu, protože ta není přímo v Local Report podporována. Máme v podstatě dvě možnosti:

První možnost je vytvořit pro každou požadovanou jazykovou verzi report nový tj. kopii rdlc souboru s jinými texty. Při načítání definice reportu (v příkladu to bylo metodou GetReportDefinition na třídě ReportDocument) pak načíst verzi podle nastavené kultury CurrentUICulture. Toto řešení má nevýhodu v tom, že pokud se bude report měnit, tak změny musíme dělat vícekrát (v každé verzi), a postupem času se můžeme dostat k rozdílným reportům. Výhodou tohoto řešení někdy může být to, že můžeme udělat pro každý jazyk jiný design tj. jinak velké pole, jiné obrázky (loga) atd.

Druhé řešení spočívá v tom, že report bude pouze jeden, ale využijeme toho, že rdlc soubor je ve formátu XML a před jeho zobrazením provedeme vlastním kódem jeho překlad. Postup bude následující:

  • V reportu si všechna pole typu Value, Label, ToolTip a Description co budeme chtít lokalizovat označíme identifikátorem pro resource. K tomu v reportu využijeme vlastnost ValueLocID resp. ToolTipLocID a LabelLocID (tyto vlastnosti jsou v designeru k dispozici, ale sám ReportViewer je nevyužívá).
  • Při načítání reportu nejprve jeho definici načteme jako XML dokument do .NET objektu XDocument.
  • V XML vyhledáme elementy Value, ToolTip, Label, Description.
  • Pokud budou mít nastaven atribut LocID, tak jeho hodnotu použijeme jako klíč do resource
  • Zaměníme hodnotu elementu za hodnotu z resource.

O něco složitější to ještě budeme mít s výrazy např.: ‘="Vytisknuto: + Globals!ExecutionTime’. Takový výraz můžeme přepsat na použití String.Format tj.:

=string.Format("Vytisknuto: {0}", Globals!ExecutionTime)

zde pak můžeme při překladu textu provést záměnu “natvrdo” prvního parametru. V tomto případě bude v resource podle LocID překlad řetězce “Vytisknuto: {0}” (tedy včetně {0}).

Kód načtení definice a provedení lokalizace vložíme do třídy ReportDefinition:

internal class ReportDefinition
{
    #region constants
    private const string cReportTagName = "Report";
    #endregion

    #region member varible and default property initialization
    private XDocument XmlDocument;

    private XNamespace ns;
    #endregion

    #region constructors and destructors
    private ReportDefinition(XDocument reportDefinitionXml)
    {
        this.XmlDocument = reportDefinitionXml;
        this.ns = reportDefinitionXml.Root.GetDefaultNamespace();
    }
    #endregion

    #region action methods
    /// <summary>
    /// Načtení XML definice reportu ze streamu
    /// </summary>
    /// <param name="inputStream">Vstupní streamu</param>
    /// <returns>ReportDefinition</returns>
    /// <exception cref="InvalidOperationException">Throws when <c>ReportDefinition XML</c> is invalid.</exception>
    public static ReportDefinition Load(System.IO.Stream inputStream)
    {
        if (inputStream == null)
        {
            throw new ArgumentNullException("inputStream");
        }

        //Načtení obsahu streamu
        XDocument reportDefinitionXml = XDocument.Load(new System.IO.StreamReader(inputStream));

        //Kontrola root elementu
        if (reportDefinitionXml.Root == null || reportDefinitionXml.Root.Name.LocalName != cReportTagName)
        {
            throw new InvalidOperationException("reportDefinition");
        }

        return new ReportDefinition(reportDefinitionXml);
    }

    /// <summary>
    /// Export definice do MemoryStreamu
    /// </summary>
    /// <returns>MemoryStream s definicí reportu</returns>
    public System.IO.MemoryStream ToStream()
    {
        var outputStream = new System.IO.MemoryStream();
        using (var writer = XmlWriter.Create(outputStream))
        {
            this.XmlDocument.WriteTo(writer);
        }

        outputStream.Position = 0;
        return outputStream;
    }

    /// <summary>
    /// Překlad hodnot reportu podle nastavených LocID
    /// </summary>
    /// <param name="localizationResource">ResourceManager s texty pro lokalizaci v dané kultuře</param>
    public void Localize(System.Resources.ResourceManager localizationResource)
    {
        XNamespace rd = @"http://schemas.microsoft.com/SQLServer/reporting/reportdesigner";

        if (localizationResource != null)
        {
            //Go through the nodes of XML document and localize
            //the text of nodes Value, ToolTip, Label, Description 
            foreach (XElement el in from i in this.XmlDocument.Root.Descendants()
                                    where i.Name == ns + "Value" || i.Name == ns + "ToolTip" || i.Name == ns + "Label" || i.Name == ns + "Description"
                                    select i)
            {
                string value = (string)el;
                string localizedValue = null;

                if (el.Attribute(rd + "LocID") != null)
                {
                    //Lokalizace textů podle nalezeného LocID
                    localizedValue = GetLocalizedValue((string)el.Attribute(rd + "LocID"), value, localizationResource);
                }

                if (localizedValue != null)
                {
                    el.Value = localizedValue;
                }
            }
        }
    }
    #endregion

    #region private member functions
    private static string GetLocalizedValue(string locID, string originalValue, System.Resources.ResourceManager localizationResource)
    {
        //Lokalizace textu
        if (string.IsNullOrEmpty(originalValue) || !originalValue.StartsWith("="))
        {
            return localizationResource.GetString(locID);
        }

        //Lokalizace hodnoty string.Format. Např.: =string.Format("from: {0} to: {1}", from, to)
        if (originalValue.StartsWith("=string.Format(\"", StringComparison.OrdinalIgnoreCase))
        {
            string text = localizationResource.GetString(locID);
            if (text == null)
            {
                return null;
            }

            int index = originalValue.IndexOf("\",", 16);
            if (index == -1)
            {
                return null;
            }

            return "=string.Format(" + Escape(text) + originalValue.Substring(index + 1);
        }

        return null;
    }

    private static string Escape(string input)
    {
        var writer = new System.IO.StringWriter();
        var provider = new Microsoft.CSharp.CSharpCodeProvider();
        provider.GenerateCodeFromExpression(new System.CodeDom.CodePrimitiveExpression(input), writer, null);
        return writer.GetStringBuilder().ToString();
    }
    #endregion
}

Třída obsahuje metodu Load pro načtení definice, metodu ToStream pro export definice zpět do streamu (použije MemoryStream) a metodu Localize, která provede překlad textů reportu. Pro nalezené elementy s LocID se zde volá pomocná metoda GetLocalizedValue, která načte hodnotu z resource, kterou buď vrátí nebo provede záměnu v případě výrazu =String.Format.

Volání třídy umístíme před načtení LocalReport objektu, v našem příkladu z minula to je v konstruktoru třídy ReportDocument:

//Načtení XML definice reportu ze streamu
var reportDefinition = ReportDefinition.Load(GetReportDefinition(reportName, reportsAssembly, reportsNamespace));
//Lokalikace XML definice
reportDefinition.Localize(GetLocalizationResource(reportsAssembly));
//Export upravené definice do MemoryStreamu a načtení reportu
localReport.LoadReportDefinition(reportDefinition.ToStream());

Pro načtení resource zde používám pomocnou metodu GetLocalizationResource:

private static System.Resources.ResourceManager GetLocalizationResource(Assembly assembly)
{
    string manifest = assembly.GetManifestResourceNames().FirstOrDefault(s => s.EndsWith(".Properties.Resources.resources", StringComparison.OrdinalIgnoreCase));
    if (manifest != null)
    {
        //Odstranění ".resources"
        string resourceSource = manifest.Substring(0, manifest.Length - ".resources".Length);

        return new System.Resources.ResourceManager(resourceSource, assembly);
    }

    return null;
}

image

Zdrojové soubory rozšířeného příkladu jsou ke stažení zde:

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback