rdlc报表多语言支持实现方案-以xml节点更新方式
序章:建多个报表实现多语言怎么样?
要实现报表的多语言支持,比如要支持中英文,往往我们会考虑建2个报表,运行时让程序去选择加载哪一个版本的报表,来实现多语言.
比如我们可以建2个dataset相同的报表:employee_en.rdlc,employee_cn.rdlc
那么运行的时候动态设定reportViewer的ReportPath就可以:如下
- switch(cultureLanguage)
- {
- case “en-US”:reportViewer.LocalReport.ReportPath=“employee_en.rdlc”;break;
- case “zh-CN”:reportViewer.LocalReport.ReportPath=“employee_cn.rdlc”;break;
- }
ok,这样实现了多语言支持,但是每当需要修改报表的时候,你会发现很恼火,每次都需要同时修改2个文件,即时调整一个column的位置,为了保持统一,你也要去修改2次,而当你忘了修改其中一个文件的时候,问题就大条了,2个版本的报表不一致.
分析
如果只需要1个rdlc文件,运行时动态翻译报表就好了.有没有可能呢,我们来建一个报表employee.rdlc,打开这个rdlc文件看下,如图:
发现了没,这完全是一个标准的xml文档,报表的所有的信息都在xml内,那么我们直接修改xml内的信息,完全可以依我们需求随意编辑这个报表,当然翻译不再话下.
怎么让reportViewer加载xml文档作为报表呢,可以采用reportViewer.LocalReport.LoadDefinition(MemoryStream ms) 的方式来加载xml文档,首先把xml文档保存为memoryStream数据流,然后传递给LocalReport就实现了动态加载xml文档,如下图.
下面的问题就是xml的翻译问题了,rdlc文字不管是body还是header,footer,文字都以textbox 节点
保存在xml文档里,如下为其中一个textbox
- <Textbox Name=“textbox6”>
- <Left>11.5cm</Left>
- <Top>0.75cm</Top>
- <rd:DefaultName>textbox6</rd:DefaultName>
- <ZIndex>2</ZIndex>
- <Width>5.25cm</Width>
- <Style>
- <PaddingLeft>2pt</PaddingLeft>
- <PaddingBottom>2pt</PaddingBottom>
- <PaddingRight>2pt</PaddingRight>
- <PaddingTop>2pt</PaddingTop>
- </Style>
- <CanGrow>true</CanGrow>
- <Height>0.63492cm</Height>
- <Value>=”Time :”+Globals!ExecutionTime</Value>
- </Textbox>
宽高等子节点我们不需要去处理,子节点元素value就是报表上的文字,就是我们需要处理.
在Textbox的vlaue数值可以分为4类,
1类是空的:如<Value/>,就是不会显示在报表上,可以跳过
2类是数据:如<Value>=Fields!EmployeeNo.Value</Value>,这些是报表数据,跳过,注:第一个字符为”=”
3类是文本:columnName也是文本,如<Value>EmployeeNo</Value>,这些正是我们需要翻译的.注:第一个字符没有”=”号
4类是文本+数据:如<Value>=”Time:”+Globals!ExecutionTime</Value>,双引号引的为文本,我们需要跳出里面的文本,来翻译.
我们需要的翻译后2种,首先把rdlc作为xml文档读取,通过xml的xpath来获得所有Value非空的TextBox 节点
注意:由于rdlc使用的是特定的namespace,所有我们在使用xpath查询的时候必须先添加这个namespace,如下:
- XmlDocument xmlDoc = new XmlDocument();
- xmlDoc.Load (AppDomain.CurrentDomain.BaseDirectory + rptPath);
- string xPath = @“//nsm:ReportItems/nsm:Textbox[nsm:Value!=”]/nsm:Value”;
- XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
- nsmgr.AddNamespace(“nsm”, “http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition”);
- XmlNodeList list=xmlDoc.SelectNodes(xPath,nsmgr);
遍历这些节点,如果节点Value为文本,直接翻译,如果为文本加数据,通过正则表达式获得文本再翻译,代码如下:
- private static void TranslateNodes(XmlNodeList list)
- {
- foreach (XmlNode node in list)
- {
- if (node is XmlElement)
- {
- string text = node.InnerText.Trim();
- if (text.Length > 0)
- {
- if (text[0] == ‘=’)
- {
- node.InnerText = TranslateLabelinQuatation(text);
- }
- else node.InnerText = Language.Translate.GetString(text);
- }
- }
- }
- }
- private static string TranslateLabelinQuatation(string text)
- {
- string pattern= “(?<=/”)[^/“]+(?=/”)(?#get the string in double quoatation)”;
- MatchCollection mc = Regex.Matches(text, pattern);
- text=Regex.Replace(text, pattern, CorrectString);
- return text;
- }
- private static string CorrectString(Match match)
- {
- string str = match.ToString();
- if (str.Trim().Length > 0) str = Language.Translate.GetString(str);
- return str;
- }
xml翻译完成,剩下的就是把xml保存为Memory Stream,并传递给reportViewer,代码如下:
- MemoryStream ms=new MemoryStream ();
- xmlDoc.Save(ms);
- ms.Position = 0;//Do not forget this line
- viewer.LocalReport.ReportPath = string.Empty;//must ensure the path is empty.
- viewer.LocalReport.LoadReportDefinition(ms);
ok,最后的代码就是普通的插入数据,设置格式,显示报表了,
来,看下成果吧,
中文版:
英文版:
另一个源码例子供参考
通过利用ReportViewer.LocalReport.LoadReportDefinition()函数,动态加载报表定义,该函数支持从MemoryStream中加载报表定义,同时RDLC文件是标准的XML文档.
至于我们如何定义在报表中需要进行多语言转换的标识文本呢,我们知道RDLC中的TEXTBOX文本会有一个Name的属性,我们会根据Name属性和Resource中的Key的值对应起来,这样我们就很清晰明确地知道哪些文本是需要转换成多语言的。
【建议:对于存储在Resource中的信息,最好跟Report的ID关联起来,如Key的ID取名为ReportName_lbl0001,这样我们就知道ReportName中的lbl0001对应于Resource文档中的哪一个值。】
Dim xmldoc As New XmlDocument
‘对XML文档的方式读取RDLC文档
xmldoc.Load(Server.MapPath(“~“) + “/Report.rdlc“)
Dim hashTran As New Hashtable
‘RDLC控件的Name和Resource的Key值对
hashTran(“textbox3“) = “V0001“
hashTran(“textbox4“) = “V0002“
hashTran(“textbox5“) = “V0003“
hashTran(“textbox1“) = “V0004“
hashTran(“textbox2“) = “V0005“
‘遍历XML文档中的节点和转换成多语言的值
TraversalXMLNodes2Translate(xmldoc, hashTran, “ReportResource“)
xmldoc.Save(ms)
ms.Position = 0
Me.ReportViewer1.LocalReport.ReportPath = String.Empty
‘动态加载报表定义
Me.ReportViewer1.LocalReport.LoadReportDefinition(ms)
2 For Each node As XmlNode In pXmlDoc.DocumentElement.ChildNodes
3 If node.HasChildNodes Then
4 TraversalXMLChildrenNodes2Translate(node, pHash, pResourceFile)
5 End If
6 Next
7 End Sub
Try
Dim strValue As String = String.Empty
If TypeOf (pNode) Is XmlElement And pNode.Name.ToUpper.Equals(“TEXTBOX“) Then
strValue = pNode.Attributes(“Name“).Value
For Each node As XmlNode In pNode.ChildNodes
If node.Name.ToUpper.Equals(“VALUE“) Then
If node.InnerText.Trim.Length > 0 _
AndAlso node.InnerText(0) <> “=“ _
AndAlso pHash IsNot Nothing _
AndAlso pHash.Contains(strValue) Then
strValue = GetValueFromResourceFile(pResourceFile, pHash(strValue))
If Not String.IsNullOrEmpty(strValue) Then
node.InnerText = strValue
End If
End If
Exit For
End If
Next
ElseIf pNode.HasChildNodes Then
For Each cNode As XmlNode In pNode.ChildNodes
TraversalXMLChildrenNodes2Translate(cNode, pHash, pResourceFile)
Next
End If
Catch ex As Exception
End Try
End Sub
Try
Return CStr(GetGlobalResourceObject(pResourceFile, key))
Catch ex As Exception
Return String.Empty
End Try
End Function