net - reemplazar texto c#
OpenXML reemplaza el texto en todos los documentos (3)
Su código no funciona porque el elemento de tabla ( w:tbl
) no está contenido en un elemento de párrafo ( w:p
). Consulte el siguiente artículo de MSDN para obtener más información.
La clase de Text
(serializada como w:t
) generalmente representa texto literal dentro de un elemento Run
en un documento de Word. De modo que simplemente podría buscar todos los elementos w:t
(clase de Text
) y reemplazar su etiqueta si el elemento de texto ( w:t
) contiene su etiqueta:
using (WordprocessingDocument doc = WordprocessingDocument.Open("yourdoc.docx", true))
{
var body = doc.MainDocumentPart.Document.Body;
foreach (var text in body.Descendants<Text>())
{
if (text.Text.Contains("##Text1##"))
{
text.Text = text.Text.Replace("##Text1##", "NewText");
}
}
}
Tengo el fragmento de código a continuación. Me gustaría reemplazar el texto "Text1" por "NewText", eso es trabajo. Pero cuando coloco el texto "Texto1" en una tabla que ya no funciona para el "Texto1" dentro de la tabla.
Me gustaría hacer este reemplazo en el documento completo.
using (WordprocessingDocument doc = WordprocessingDocument.Open(String.Format("c://temp//filename.docx"), true))
{
var body = doc.MainDocumentPart.Document.Body;
foreach (var para in body.Elements<Paragraph>())
{
foreach (var run in para.Elements<Run>())
{
foreach (var text in run.Elements<Text>())
{
if (text.Text.Contains("##Text1##"))
text.Text = text.Text.Replace("##Text1##", "NewText");
}
}
}
}
Tal vez esta solución es más fácil
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(document, true))
{
string docText = null;
//1. Copy all the file into a string
using (StreamReader sr = new StreamReader(wordDoc.MainDocumentPart.GetStream()))
docText = sr.ReadToEnd();
//2. Use regular expression to replace all text
Regex regexText = new Regex(find);
docText = regexText.Replace(docText, replace);
//3. Write the changed string into the file again
using (StreamWriter sw = new StreamWriter(wordDoc.MainDocumentPart.GetStream(FileMode.Create)))
sw.Write(docText);
Tomando prestadas algunas otras respuestas en varios lugares, y con el hecho de que se deben superar cuatro obstáculos principales:
- Elimine cualquier carácter de caracteres de alto nivel de Unicode de la cadena de reemplazo que no se pueda leer desde Word (de la entrada incorrecta del usuario)
- Posibilidad de buscar su resultado de búsqueda en varias ejecuciones o elementos de texto dentro de un párrafo (Word a menudo dividirá una sola oración en varias ejecuciones de texto)
- Posibilidad de incluir un salto de línea en su texto de reemplazo para insertar texto de varias líneas en el documento.
- Posibilidad de pasar en cualquier nodo como el punto de partida de su búsqueda para restringir la búsqueda a esa parte del documento (como el cuerpo, el encabezado, el pie de página, una tabla específica, una fila de tabla o una tabla).
Estoy seguro de que los escenarios avanzados como los marcadores, el anidamiento complejo necesitarán más modificaciones sobre esto, pero está funcionando para los tipos de documentos de palabras básicas con los que me he topado hasta ahora, y es mucho más útil para mí que ignorar las ejecuciones por completo o usar un RegEx en todo el archivo sin posibilidad de segmentar una pieza específica de TableCell o documento (para escenarios avanzados).
Ejemplo de uso:
var body = document.MainDocumentPart.Document.Body;
ReplaceText(body, replace, with);
El código:
using System;
using System.Collections.Generic;
using System.Linq;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
namespace My.Web.Api.OpenXml
{
public static class WordTools
{
/// <summary>
/// Find/replace within the specified paragraph.
/// </summary>
/// <param name="paragraph"></param>
/// <param name="find"></param>
/// <param name="replaceWith"></param>
public static void ReplaceText(Paragraph paragraph, string find, string replaceWith)
{
var texts = paragraph.Descendants<Text>();
for (int t = 0; t < texts.Count(); t++)
{ // figure out which Text element within the paragraph contains the starting point of the search string
Text txt = texts.ElementAt(t);
for (int c = 0; c < txt.Text.Length; c++)
{
var match = IsMatch(texts, t, c, find);
if (match != null)
{ // now replace the text
string[] lines = replaceWith.Replace(Environment.NewLine, "/r").Split(''/n'', ''/r''); // handle any lone n/r returns, plus newline.
int skip = lines[lines.Length - 1].Length - 1; // will jump to end of the replacement text, it has been processed.
if (c > 0)
lines[0] = txt.Text.Substring(0, c) + lines[0]; // has a prefix
if (match.EndCharIndex + 1 < texts.ElementAt(match.EndElementIndex).Text.Length)
lines[lines.Length - 1] = lines[lines.Length - 1] + texts.ElementAt(match.EndElementIndex).Text.Substring(match.EndCharIndex + 1);
txt.Space = new EnumValue<SpaceProcessingModeValues>(SpaceProcessingModeValues.Preserve); // in case your value starts/ends with whitespace
txt.Text = lines[0];
// remove any extra texts.
for (int i = t + 1; i <= match.EndElementIndex; i++)
{
texts.ElementAt(i).Text = string.Empty; // clear the text
}
// if ''with'' contained line breaks we need to add breaks back...
if (lines.Count() > 1)
{
OpenXmlElement currEl = txt;
Break br;
// append more lines
var run = txt.Parent as Run;
for (int i = 1; i < lines.Count(); i++)
{
br = new Break();
run.InsertAfter<Break>(br, currEl);
currEl = br;
txt = new Text(lines[i]);
run.InsertAfter<Text>(txt, currEl);
t++; // skip to this next text element
currEl = txt;
}
c = skip; // new line
}
else
{ // continue to process same line
c += skip;
}
}
}
}
}
/// <summary>
/// Determine if the texts (starting at element t, char c) exactly contain the find text
/// </summary>
/// <param name="texts"></param>
/// <param name="t"></param>
/// <param name="c"></param>
/// <param name="find"></param>
/// <returns>null or the result info</returns>
static Match IsMatch(IEnumerable<Text> texts, int t, int c, string find)
{
int ix = 0;
for (int i = t; i < texts.Count(); i++)
{
for (int j = c; j < texts.ElementAt(i).Text.Length; j++)
{
if (find[ix] != texts.ElementAt(i).Text[j])
{
return null; // element mismatch
}
ix++; // match; go to next character
if (ix == find.Length)
return new Match() { EndElementIndex = i, EndCharIndex = j }; // full match with no issues
}
c = 0; // reset char index for next text element
}
return null; // ran out of text, not a string match
}
/// <summary>
/// Defines a match result
/// </summary>
class Match
{
/// <summary>
/// Last matching element index containing part of the search text
/// </summary>
public int EndElementIndex { get; set; }
/// <summary>
/// Last matching char index of the search text in last matching element
/// </summary>
public int EndCharIndex { get; set; }
}
} // class
} // namespace
public static class OpenXmlTools
{
// filters control characters but allows only properly-formed surrogate sequences
private static Regex _invalidXMLChars = new Regex(
@"(?<![/uD800-/uDBFF])[/uDC00-/uDFFF]|[/uD800-/uDBFF](?![/uDC00-/uDFFF])|[/x00-/x08/x0B/x0C/x0E-/x1F/x7F-/x9F/uFEFF/uFFFE/uFFFF]",
RegexOptions.Compiled);
/// <summary>
/// removes any unusual unicode characters that can''t be encoded into XML which give exception on save
/// </summary>
public static string RemoveInvalidXMLChars(string text)
{
if (string.IsNullOrEmpty(text)) return "";
return _invalidXMLChars.Replace(text, "");
}
}