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
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;
}
Zdrojové soubory rozšířeného příkladu jsou ke stažení zde: