序章:建多个报表实现多语言怎么样?

   要实现报表的多语言支持,比如要支持中英文,往往我们会考虑建2个报表,运行时让程序去选择加载哪一个版本的报表,来实现多语言.

   比如我们可以建2个dataset相同的报表:employee_en.rdlc,employee_cn.rdlc

   那么运行的时候动态设定reportViewer的ReportPath就可以:如下

[c-sharp] view plain copy

  1. switch(cultureLanguage)  
  2. {  
  3.    case “en-US”:reportViewer.LocalReport.ReportPath=“employee_en.rdlc”;break;  
  4.    case “zh-CN”:reportViewer.LocalReport.ReportPath=“employee_cn.rdlc”;break;  
  5. }  

   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

 

[xhtml] view plain copy

  1. <Textbox Name=“textbox6”>  
  2.         <Left>11.5cm</Left>  
  3.         <Top>0.75cm</Top>  
  4.         <rd:DefaultName>textbox6</rd:DefaultName>  
  5.         <ZIndex>2</ZIndex>  
  6.         <Width>5.25cm</Width>  
  7.         <Style>  
  8.           <PaddingLeft>2pt</PaddingLeft>  
  9.           <PaddingBottom>2pt</PaddingBottom>  
  10.           <PaddingRight>2pt</PaddingRight>  
  11.           <PaddingTop>2pt</PaddingTop>  
  12.         </Style>  
  13.         <CanGrow>true</CanGrow>  
  14.         <Height>0.63492cm</Height>  
  15.         <Value>=”Time :”+Globals!ExecutionTime</Value>  
  16.       </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,如下:

[c-sharp] view plain copy

  1. XmlDocument xmlDoc = new XmlDocument();  
  2.             xmlDoc.Load (AppDomain.CurrentDomain.BaseDirectory + rptPath);  
  3.  string xPath = @“//nsm:ReportItems/nsm:Textbox[nsm:Value!=”]/nsm:Value”;  
  4.             XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);  
  5.             nsmgr.AddNamespace(“nsm”“http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition”);  
  6.             XmlNodeList list=xmlDoc.SelectNodes(xPath,nsmgr);       

遍历这些节点,如果节点Value为文本,直接翻译,如果为文本加数据,通过正则表达式获得文本再翻译,代码如下:

[c-sharp] view plain copy

  1. private static   void TranslateNodes(XmlNodeList list)  
  2. {  
  3.      foreach (XmlNode node in list)  
  4.             {  
  5.                 if (node is XmlElement)  
  6.                 {  
  7.                     string text = node.InnerText.Trim();  
  8.                     if (text.Length > 0)  
  9.                     {  
  10.                         if (text[0] == ‘=’)  
  11.                         {  
  12.                             node.InnerText = TranslateLabelinQuatation(text);  
  13.                         }  
  14.                         else  node.InnerText = Language.Translate.GetString(text);  
  15.                     }  
  16.                 }  
  17.             }             
  18.         }  
  19.         private static string  TranslateLabelinQuatation(string text)  
  20.         {  
  21.             
  22.             string pattern= “(?<=/”)[^/“]+(?=/”)(?#get the string in double quoatation)”;  
  23.             MatchCollection mc = Regex.Matches(text, pattern);  
  24.             text=Regex.Replace(text, pattern, CorrectString);  
  25.             return text;              
  26.         }  
  27.          private static string CorrectString(Match match)  
  28.         {  
  29.             string str = match.ToString();  
  30.             if (str.Trim().Length > 0) str = Language.Translate.GetString(str);  
  31.             return str;  
  32.         }  

 

xml翻译完成,剩下的就是把xml保存为Memory Stream,并传递给reportViewer,代码如下:

[c-sharp] view plain copy

  1. MemoryStream ms=new MemoryStream ();  
  2. xmlDoc.Save(ms);  
  3. ms.Position = 0;//Do not forget this line  
  4. viewer.LocalReport.ReportPath = string.Empty;//must ensure the path is empty.  
  5. viewer.LocalReport.LoadReportDefinition(ms);  

ok,最后的代码就是普通的插入数据,设置格式,显示报表了,

            ReportDataSource rds = new ReportDataSource(DataSetName, ds.Tables[0]);  
            viewer.LocalReport.DataSources.Add(rds);  
            viewer.SetDisplayMode(DisplayMode.PrintLayout);  
            viewer.ZoomMode = ZoomMode.Percent;  
            viewer.ZoomPercent = 100;  

 

来,看下成果吧,

中文版:

 

英文版:

  


另一个源码例子供参考

  通过利用ReportViewer.LocalReport.LoadReportDefinition()函数,动态加载报表定义,该函数支持从MemoryStream中加载报表定义,同时RDLC文件是标准的XML文档.

  至于我们如何定义在报表中需要进行多语言转换的标识文本呢,我们知道RDLC中的TEXTBOX文本会有一个Name的属性,我们会根据Name属性和Resource中的Key的值对应起来,这样我们就很清晰明确地知道哪些文本是需要转换成多语言的。

  【建议:对于存储在Resource中的信息,最好跟Report的ID关联起来,如Key的ID取名为ReportName_lbl0001,这样我们就知道ReportName中的lbl0001对应于Resource文档中的哪一个值。】

            Dim ms As New MemoryStream
            
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)

 

1  Private Sub TraversalXMLNodes2Translate(ByRef pXmlDoc As XmlDocument, ByVal pHash As Hashtable, ByRef pResourceFile As String)
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

 

    Private Sub TraversalXMLChildrenNodes2Translate(ByRef pNode As XmlNode, ByRef pHash As Hashtable, ByRef pResourceFile As String)
        
Try
            
Dim strValue As String = String.Empty
            
If TypeOf (pNode) Is XmlElement And pNode.Name.ToUpper.Equals(TEXTBOXThen
                strValue 
= pNode.Attributes(Name).Value
                
For Each node As XmlNode In pNode.ChildNodes
                    
If node.Name.ToUpper.Equals(VALUEThen
                        
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

 

    Private Function GetValueFromResourceFile(ByRef pResourceFile As StringByVal key As StringAs String
        
Try
            
Return CStr(GetGlobalResourceObject(pResourceFile, key))
        
Catch ex As Exception
            
Return String.Empty
        
End Try
    
End Function