替换文本,在平时很简单的问题,如果通过wordProcessingML操作,可能就变成word操作中最复杂的问题了。
因为字体、拼写检测等细微的差别,word中在一起的字符串,转到xml时有可能会封装到几个<w:r>中。这就给替换带来很大难度,使原本简单的问题变的复杂。
如:[pr:test/]有可能会被分解到三个<w:r>,还可能更多。最多会分散到n!个<w:r>中,n是标记包含的字符数。
这就给找到匹配增加了难度,也很难保留原来的word格式。
为了实现Doc的随机读写,只能用xpath,下面是[pr:test/]在word xml中的一种情况
<w:r>
<w:rPr>
<w:rFonts w:ascii="新宋体" w:fareast="新宋体"/>
<wx:font wx:val="新宋体" xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"/>
<w:noProof/>
<w:color w:val="A31515"/>
<w:kern w:val="0"/>
<w:sz w:val="18"/>
<w:sz-cs w:val="18"/>
</w:rPr>
<w:t>[pr</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="新宋体" w:fareast="新宋体" w:hint="fareast"/>
<wx:font wx:val="新宋体" xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"/>
<w:noProof/>
<w:color w:val="A31515"/>
<w:kern w:val="0"/>
<w:sz w:val="18"/>
<w:sz-cs w:val="18"/>
</w:rPr>
<w:t>:</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="新宋体" w:fareast="新宋体"/>
<wx:font wx:val="新宋体" xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"/>
<w:noProof/>
<w:color w:val="A31515"/>
<w:kern w:val="0"/>
<w:sz w:val="18"/>
<w:sz-cs w:val="18"/>
</w:rPr>
<w:t>test/]</w:t>
</w:r>
<w:rPr>
<w:rFonts w:ascii="新宋体" w:fareast="新宋体"/>
<wx:font wx:val="新宋体" xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"/>
<w:noProof/>
<w:color w:val="A31515"/>
<w:kern w:val="0"/>
<w:sz w:val="18"/>
<w:sz-cs w:val="18"/>
</w:rPr>
<w:t>[pr</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="新宋体" w:fareast="新宋体" w:hint="fareast"/>
<wx:font wx:val="新宋体" xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"/>
<w:noProof/>
<w:color w:val="A31515"/>
<w:kern w:val="0"/>
<w:sz w:val="18"/>
<w:sz-cs w:val="18"/>
</w:rPr>
<w:t>:</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="新宋体" w:fareast="新宋体"/>
<wx:font wx:val="新宋体" xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"/>
<w:noProof/>
<w:color w:val="A31515"/>
<w:kern w:val="0"/>
<w:sz w:val="18"/>
<w:sz-cs w:val="18"/>
</w:rPr>
<w:t>test/]</w:t>
</w:r>
我到目前想到了三种解决方案:
第一种最快,但是他只能解决标记位于一个<w:r>中的情况:
public string ReplaceMarker(MarkerValueCollection mvc,bool isVerify)
{
_mvc = mvc;
string[] marker = mvc.AllKeys;
if (isVerify)
{
VerifyMarker(marker);
}
string text = _wordParser.WordDoc.InnerXml;
MatchEvaluator me = new MatchEvaluator(ReplaceText);
string newtext = Regex.Replace(text, _findMarker, me);
return newtext;
}
private string ReplaceText(Match m)
{
if (_mvc.Get(new CommonMarker(m.ToString())) == null)
{
return m.ToString();
}
else
{
return _mvc.Get(new CommonMarker(m.ToString()));
}
}
{
_mvc = mvc;
string[] marker = mvc.AllKeys;
if (isVerify)
{
VerifyMarker(marker);
}
string text = _wordParser.WordDoc.InnerXml;
MatchEvaluator me = new MatchEvaluator(ReplaceText);
string newtext = Regex.Replace(text, _findMarker, me);
return newtext;
}
private string ReplaceText(Match m)
{
if (_mvc.Get(new CommonMarker(m.ToString())) == null)
{
return m.ToString();
}
else
{
return _mvc.Get(new CommonMarker(m.ToString()));
}
}
因为假设标记位于是连续的,所以把xml当作纯文本,用正则表达式替换就可以了。
第二中方案,可以解决段落中含有[],[/],[[pr:test/]/]的情况,标记中的个字符可以分散到任意个<w:r>,但是要求段中不能包含不匹配的[和]。
/// <summary>
/// 替换给定段落中的值一次
/// </summary>
/// <param name="paragraph">段落节点</param>
/// <param name="marker">标记</param>
/// <param name="value">替换的值</param>
/// <returns></returns>
///<remarks>标记的begin,end不能有不匹配。</remarks>
private XmlNode ReplaceMarkerOnce(XmlNode paragraph, string marker, string value)
{
CommonMarker cmarker = new CommonMarker(marker);
XmlNodeList wrs = ((XmlElement)paragraph).GetElementsByTagName("w:r");
for (int i = 0; i < wrs.Count; i++)
{
if (wrs[i].InnerText.IndexOf(marker) >= 0)
{
wrs[i].InnerXml = string.Format(@"<w:t>{0}</w:t>", wrs[i].InnerText.Replace(marker, value));
}
}
IList<int> begins = new List<int>();
IList<int> ends = new List<int>();
for (int i = 0; i < wrs.Count; i++)
{
if (wrs[i].InnerText.IndexOf(cmarker.Begin) >= 0)
{
begins.Add(i);
}
if (wrs[i].InnerText.IndexOf(cmarker.End) >= 0)
{
ends.Add(i);
}
}
if (begins.Count != ends.Count)
{
throw new ApplicationException("段落中有不匹配的标记");
}
else
{
for (int i = 0; i < begins.Count; i++)
{
string t = "";
Stack<XmlNode> rs = new Stack<XmlNode>();
for (int j = begins[i]; j <= ends[i]; j++)
{
rs.Push(wrs[j]);
t = t + wrs[j].InnerText;
}
if (t.IndexOf(cmarker.ToString()) >= 0)
{
XmlNode xn = rs.Pop();//把最后一个r保留
//替换
xn.InnerXml = string.Format(@"<w:t>{0}</w:t>", t.Replace(cmarker.ToString(), value));
while (rs.Count > 0)
{
XmlNode r = rs.Pop(); //移出的r
paragraph.RemoveChild(r);
}
return paragraph;
}
}
}
return paragraph;
}
/// <summary>
/// 替换给定段落中的标记
/// </summary>
/// <param name="paragraph">段落节点</param>
/// <param name="marker">标记</param>
/// <param name="value">替换的值</param>
/// <returns></returns>
///<remarks>标记的begin,end不能有不匹配。</remarks>
public XmlNode ReplaceMarker(XmlNode paragraph, string marker, string value)
{
CommonMarker cmarker = new CommonMarker(marker);
string x = paragraph.InnerText;
int count = Regex.Matches(x, cmarker.Reg).Count;
XmlNode xn = paragraph;
for (int i = 0; i < count; i++)
{
replce(xn, marker, value);
xn = ReplaceMarkerOnce(xn, cmarker.ToString(), value);
}
/// 替换给定段落中的值一次
/// </summary>
/// <param name="paragraph">段落节点</param>
/// <param name="marker">标记</param>
/// <param name="value">替换的值</param>
/// <returns></returns>
///<remarks>标记的begin,end不能有不匹配。</remarks>
private XmlNode ReplaceMarkerOnce(XmlNode paragraph, string marker, string value)
{
CommonMarker cmarker = new CommonMarker(marker);
XmlNodeList wrs = ((XmlElement)paragraph).GetElementsByTagName("w:r");
for (int i = 0; i < wrs.Count; i++)
{
if (wrs[i].InnerText.IndexOf(marker) >= 0)
{
wrs[i].InnerXml = string.Format(@"<w:t>{0}</w:t>", wrs[i].InnerText.Replace(marker, value));
}
}
IList<int> begins = new List<int>();
IList<int> ends = new List<int>();
for (int i = 0; i < wrs.Count; i++)
{
if (wrs[i].InnerText.IndexOf(cmarker.Begin) >= 0)
{
begins.Add(i);
}
if (wrs[i].InnerText.IndexOf(cmarker.End) >= 0)
{
ends.Add(i);
}
}
if (begins.Count != ends.Count)
{
throw new ApplicationException("段落中有不匹配的标记");
}
else
{
for (int i = 0; i < begins.Count; i++)
{
string t = "";
Stack<XmlNode> rs = new Stack<XmlNode>();
for (int j = begins[i]; j <= ends[i]; j++)
{
rs.Push(wrs[j]);
t = t + wrs[j].InnerText;
}
if (t.IndexOf(cmarker.ToString()) >= 0)
{
XmlNode xn = rs.Pop();//把最后一个r保留
//替换
xn.InnerXml = string.Format(@"<w:t>{0}</w:t>", t.Replace(cmarker.ToString(), value));
while (rs.Count > 0)
{
XmlNode r = rs.Pop(); //移出的r
paragraph.RemoveChild(r);
}
return paragraph;
}
}
}
return paragraph;
}
/// <summary>
/// 替换给定段落中的标记
/// </summary>
/// <param name="paragraph">段落节点</param>
/// <param name="marker">标记</param>
/// <param name="value">替换的值</param>
/// <returns></returns>
///<remarks>标记的begin,end不能有不匹配。</remarks>
public XmlNode ReplaceMarker(XmlNode paragraph, string marker, string value)
{
CommonMarker cmarker = new CommonMarker(marker);
string x = paragraph.InnerText;
int count = Regex.Matches(x, cmarker.Reg).Count;
XmlNode xn = paragraph;
for (int i = 0; i < count; i++)
{
replce(xn, marker, value);
xn = ReplaceMarkerOnce(xn, cmarker.ToString(), value);
}