Use Values of Project/Properties/Settings.settings in InnoSetup script? - c#

I want to be able to change a value in one place in my C# .NET 4.0 Project. For this I use the built in Properties/Settings.settings file, which is essentially an XML file.
As we're using InnoSetup (which is pretty common) for our software, and as the settings.settings file is the default way of storing values for a C# .NET application, I wondered if there was a way for the InnoSetup script to pull values from the settings file, or if you could point me to a script that could do this to set variables of the setup script.
EDIT:
I got the XML example running, but now I cannot use an XPath query to get the correct node. This is my script:
[Code]
{--- MSXML ---}
const
XMLFileName = '..\CCFinderWPF\Properties\Settings.settings';
XMLFileName2 = '..\CCFinderWPF\Properties\Settings.xml';
function Settings(Default: String): String;
var
XMLDoc, SeekedTopNode, SeekedNode, iNode, Sel: Variant;
Path, XPath: String;
begin
{ Load the XML File }
try
XMLDoc := CreateOleObject('MSXML2.DOMDocument.4.0');
except
RaiseException('Please install MSXML first.'#13#13'(Error ''' + GetExceptionMessage + ''' occurred)');
end;
XMLDoc.async := False;
XMLDoc.resolveExternals := false;
XMLDoc.preserveWhiteSpace := true;
XMLDoc.setProperty('SelectionLanguage', 'XPath');
XMLDoc.load(XMLFileName);
if XMLDoc.parseError.errorCode <> 0 then
RaiseException('Error on line ' + IntToStr(XMLDoc.parseError.line) + ', position ' + IntToStr(XMLDoc.parseError.linepos) + ': ' + XMLDoc.parseError.reason);
MsgBox('XML-File: ' + XMLFileName, mbInformation, MB_OK);
{ Modify the XML document }
iNode := XMLDoc.documentElement;
iNode := iNode.selectSingleNode('Setting[#Name="' + Default + '"]');
// **selectSingleNode returns null, seems that selectSingleNode with XPath doesn't work?**
MsgBox('The Node is: ' + iNode.nodeName, mbInformation, MB_OK);
SeekedNode := iNode.firstChild;
Result := SeekedNode.lastChild.text;
MsgBox('The XPath is: ' + XPath, mbInformation, MB_OK);
end;
I call this function using the Inno Setup Precompiler like this:
#define ABAppName "{code:Settings|AppName}"
The XML-file looks like this:
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="CCFinder.Properties" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="AppName" Type="System.String" Scope="Application">
<Value Profile="(Default)">CCFinder</Value>
</Setting>
...
The goal of all this is that I can set values in my app from the Settings.settings file in my C# projects, have my hudson instance checkout my code and change this XML file for different versions of the app and for example change some values that I don't necessary know at design time. I want to be able to pull my variables from this XML file. I'm just stuck using MSXML2 here. Any help would be greatly appreciated.

Nobody knew a solution, so I solved this with a while loop. Note that this is my first Delphi code, so it might not be elegant and / or 100% correct. This is how I define the needed constants:
#define ABAppName "{code:Settings|AppName}"
#define ABAppVersion "{code:Settings|AppVersion}"
#define ABCompany "{code:Settings|CompanyName}"
#define ABAppTitle "{code:Settings|ApplicationTitle}"
#define ABAppTitlePro "{code:Settings|ApplicationTitle)}"+" "+"{code:Settings|ProVersionAppender}"
#define ABCompanyUrl "{code:Settings|CompanyUrl_de}"
#define ABContactEmailDe "{code:Settings|ContactEmail_de}"
#define ABYear "{code:Settings|Year}"
These are in a constants.iss file as plain text that I include in my setup-script with #include "constants.iss" in the beginning. This is the delphi-code that is called by the code-calls:
const
XMLFileName = '..\CCFinder\Properties\Settings.settings';
function Settings(Default: String): String;
var
XMLDoc, iNode: Variant;
Path: String;
i : Integer;
Loop : Boolean;
begin
{ Load the XML File }
try
XMLDoc := CreateOleObject('MSXML2.DOMDocument.6.0');
except
RaiseException('Please install MSXML first.'#13#13'(Error ''' + GetExceptionMessage + ''' occurred)');
end;
try
XMLDoc.async := False;
XMLDoc.resolveExternals := false;
XMLDoc.load(XMLFileName);
if XMLDoc.parseError.errorCode <> 0 then
RaiseException('Error on line ' + IntToStr(XMLDoc.parseError.line) + ', position ' + IntToStr(XMLDoc.parseError.linepos) + ': ' + XMLDoc.parseError.reason);
iNode := XMLDoc.DocumentElement;
iNode := iNode.LastChild;
Loop := True;
i := 0;
while Loop do
begin
Try
if iNode.ChildNodes[i].attributes[0].nodeValue = Default
then
begin
Result := iNode.ChildNodes[i].text;
// MsgBox('Result for Default: ' + Result + Default, mbInformation, MB_OK);
i := i+1;
Loop := False;
Break;
end
else
begin
i := i+1;
if i = 100 then Loop := false;
end;
except
Result := '';
end;
end;
except
end;
end;
The loop is limited by 100 runs, because I'm too clumsy to count the XML nodes in Delphi.
The InnoSetup Preprocessor needs to be installed.
Inno doesn't seem to accept constants in path names, therefore some values just could not be drawn from the Properties/Settings.settings file.
I didn't know that the preprocessor doesn't actually use the Delphi code to fetch the correct values to include them in the script, instead it includes the complete delphi code call in all places where the constant is used. Of course this isn't what I wanted to do, as it doesn't even work when the code is embedded into paths for example. Therefore I changed the setup script to only include one constant I needed to replace by find and replace, and that is used for pathnames and the executable filename for example.
For the rest, I wrote a .NET command-line application that does basically the same thing as the delphi code, to execute it in the Hudson before the setup is compiled on Hudson. This replaces the code-calls with the actual values needed (only except, but you should get the point):
{
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(pathToSettingsFile);
XmlNodeList list = xmldoc.GetElementsByTagName("Setting");
foreach (XmlNode node in list)
{
string keystring = node.Attributes["Name"].InnerText;
string valuestring = node.FirstChild.InnerText;
settingValues.Add(keystring,valuestring);
}
var t = File.OpenText(constantsfilename);
string text;
using (t)
{
text = t.ReadToEnd();
}
string sample = "{code:Settings|";
char endsign = '}';
while (text.Contains(sample))
{
int startindex = text.IndexOf(sample);
int endindex = text.IndexOf(endsign, startindex);
int variableIndex = startindex + sample.Length;
string variable = text.Substring(variableIndex, endindex - variableIndex);
text = text.Replace(sample + variable + endsign, newVal(variable));
}
var w = new StreamWriter(constantsfilename);
using (w)
{
w.Write(text);
w.Flush();
}
}
public static string newVal(string variable)
{
if (settingValues.ContainsKey(variable))
return settingValues[variable];
else
{
return "";
}
}
This means I now can set values in my Settings-File that can be changed with yet another command-line application in hudson for special builds, and gets automatically written to the setup script.
EDIT: Just realized that it's the Visual Studio Designer that manually transfers the settings from the settings-file to the app.config, which gets changed to ProgramName.exe.config file in the output while building. Therefore you needs to change the values in there to have it have an effect, the coe should work if you give it the correct path to the file.
EDIT2: This app.config is renamed during compilation into YourAppName.exe.config ... when you're not putting it inside the setup, the values are not initialized correctly. I extended my code to also adapt the designer code of the Settings.designer.cs file with the values from the XML and guess this will do it. One configuration file to rule them all :-)

Related

C# XmlReader reads XML wrong and different based on how I invoke the reader's methods

So my current understanding of how the C# XmlReader works is that it takes a given XML File and reads it node-by-node when I wrap it in a following construct:
using System.Xml;
using System;
using System.Diagnostics;
...
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreComments = true;
settings.IgnoreWhitespace = true;
settings.IgnoreProcessingInstructions = true;
using (XmlReader reader = XmlReader.Create(path, settings))
{
while (reader.Read())
{
// All reader methods I call here will reference the current node
// until I move the pointer to some further node by calling methods like
// reader.Read(), reader.MoveToContent(), reader.MoveToElement() etc
}
}
Why will the following two snippets (within the above construct) produce two very different results, even though they both call the same methods?
I used this example file for testing.
Debug.WriteLine(new string(' ', reader.Depth * 2) + "<" + reader.NodeType.ToString() + "|" + reader.Name + ">" + reader.ReadString() + "</>");
(Snippet 1)
vs
(Snippet 2)
string xmlcontent = reader.ReadString();
string xmlname = reader.Name.ToString();
string xmltype = reader.NodeType.ToString();
int xmldepth = reader.Depth;
Debug.WriteLine(new string(' ', xmldepth * 2) + "<" + xmltype + "|" + xmlname + ">" + xmlcontent + "</>");
Output of Snippet 1:
<XmlDeclaration|xml></>
<Element|rss></>
<Element|head></>
<Text|>Test Xml File</>
<Element|description>This will test my xml reader</>
<EndElement|head></>
<Element|body></>
<Element|g:id>1QBX23</>
<Element|g:title>Example Title</>
<Element|g:description>Example Description</>
<EndElement|item></>
<Element|item></>
<Text|>2QXB32</>
<Element|g:title>Example Title</>
<Element|g:description>Example Description</>
<EndElement|item></>
<EndElement|body></>
<EndElement|xml></>
<EndElement|rss></>
Yes, this is formatted as it was in my output window. As to be seen it skipped certain elements and outputted a wrong depth for a few others. Therefore, the NodeTypes are correct, unlike Snippet Number 2, which outputs:
<XmlDeclaration|xml></>
<Element|xml></>
<Element|title></>
<EndElement|title>Test Xml File</>
<EndElement|description>This will test my xml reader</>
<EndElement|head></>
<Element|item></>
<EndElement|g:id>1QBX23</>
<EndElement|g:title>Example Title</>
<EndElement|g:description>Example Description</>
<EndElement|item></>
<Element|g:id></>
<EndElement|g:id>2QXB32</>
<EndElement|g:title>Example Title</>
<EndElement|g:description>Example Description</>
<EndElement|item></>
<EndElement|body></>
<EndElement|xml></>
<EndElement|rss></>
Once again, the depth is messed up, but it's not as critical as with Snippet Number 1. It also skipped some elements and assigned wrong NodeTypes.
Why can't it output the expected result? And why do these two snippets produce two totally different outputs with different depths, NodeTypes and skipped nodes?
I'd appreciate any help on this. I searched a lot for any answers on this but it seems like I'm the only one experiencing these issues. I'm using the .NET Framework 4.6.2 with Asp.net Web Forms in Visual Studio 2017.
Firstly, you are using a method XmlReader.ReadString() that is deprecated:
XmlReader.ReadString Method
... reads the contents of an element or text node as a string. However, we recommend that you use the ReadElementContentAsString method instead, because it provides a more straightforward way to handle this operation.
However, beyond warning us off the method, the documentation doesn't precisely specify what it actually does. To determine that, we need to go to the reference source:
public virtual string ReadString() {
if (this.ReadState != ReadState.Interactive) {
return string.Empty;
}
this.MoveToElement();
if (this.NodeType == XmlNodeType.Element) {
if (this.IsEmptyElement) {
return string.Empty;
}
else if (!this.Read()) {
throw new InvalidOperationException(Res.GetString(Res.Xml_InvalidOperation));
}
if (this.NodeType == XmlNodeType.EndElement) {
return string.Empty;
}
}
string result = string.Empty;
while (IsTextualNode(this.NodeType)) {
result += this.Value;
if (!this.Read()) {
break;
}
}
return result;
}
This method does the following:
If the current node is an empty element node, return an empty string.
If the current node is an element that is not empty, advance the reader.
If the now-current node is the end of the element, return an empty string.
While the current node is a text node, add the text to a string and advance the reader. As soon as the current node is not a text node, return the accumulated string.
Thus we can see that this method is designed to advance the reader. We can also see that, given mixed-content XML like <head>text <b>BOLD</b> more text</head>, ReadString() will only partially read the <head> element, leaving the reader positioned on <b>. This oddity is likely why Microsoft deprecated the method.
We can also see why your two snippets function differently. In the first, you get reader.Depth and reader.NodeType before calling ReadString() and advancing the reader. In the second you get these properties after advancing the reader.
Since your intent is to iterate through the nodes and get the value of each, rather than ReadString() or ReadElementContentAsString() you should just use XmlReader.Value:
gets the text value of the current node.
Thus your corrected code should look like:
string xmlcontent = reader.Value;
string xmlname = reader.Name.ToString();
string xmltype = reader.NodeType.ToString();
int xmldepth = reader.Depth;
Console.WriteLine(new string(' ', xmldepth * 2) + "<" + xmltype + "|" + xmlname + ">" + xmlcontent + "</>");
XmlReader is tricky to work with. You always need to check the documentation to determine exactly where a given method positions the reader. For instance, XmlReader.ReadElementContentAsString() moves the reader past the end of the element, whereas XmlReader.ReadSubtree() moves the reader to the end of the element. But as a general rule any method named Read is going to advance the reader, so you need to be careful using a Read method inside an outer while (reader.Read()) loop.
Demo fiddle here.

splitting strings in oracle query

I want to split my strings in Oracle based on length with space as a delimiter.
For example,
`MY_STRING="Before continuing, turn off the top title display without changing its definition:"`
My output should be
`STRING1="Before continuing, turn off the"`
`STRING2="top title display without changing"`
`STRING3="its definition:"`
The strings should be a maximum of 35 characters in length. The words after position 105 can be ignored.
It colud be done with a stored function :
create or replace FUNCTION get_part(p_value IN VARCHAR2, part in number)
RETURN VARCHAR2
IS temp VARCHAR2(1000);
BEGIN
temp := p_value;
FOR i IN 1 .. (part-1) LOOP
if (Length(temp) <35) then
return '';
ELSE
FOR j in REVERSE 1 .. 35 LOOP
if SUBSTR(temp,j,1) = ' ' then
temp := SUBSTR(temp,j+1);
EXIT;
end if;
END LOOP;
temp := SUBSTR(temp,36);
end if;
END LOOP;
if (Length(temp) <=35) then
return temp;
else
FOR j in reverse 1 .. 35 LOOP
if SUBSTR(temp,j,1) = ' ' then
return SUBSTR(temp,1,j-1);
end if;
END LOOP;
return SUBSTR(temp,1,35);
end if;
END;
usage:
select
get_part(string_value,1),
get_part(string_value,2),
get_part(string_value,3) from ( select 'Before continuing, turn off the top title display without changing its definition:' string_value from dual)
It surely will fail if there are more than 35 chars without space, i'll leave that to you
EDIT: now it should split hard after 35 chars if there are no spaces
You have tagged C# so i chose the language to answer you. I hope it helps. It perfectly splits the text.
Regarding the rules, in your case it splits the text in 3 parts.
This is the Outcome :
STR1 :"Before continuing, turn off the"
STR2 :" top title display without changing"
STR3 :" its definition:"
static void Main(string[] args)
{
const string txt = "Before continuing, turn off the top title display without changing its definition:";
var txtArr = txt.ToCharArray();
var counter = 0;
var stringList = new List<string>();
var str = string.Empty;
for (var i = 0; i < txt.Count(); i++)
{
counter++;
if (counter == 35)
{
while (txtArr[i].ToString() != " ")
{
i--;
str = str.Remove(i);
}
stringList.Add(str);
str = string.Empty;
counter = 0;
}
str = str + txtArr[i];
}
stringList.Add(str);
}
This is how I implemented the algorithm in ORACLE (PL/SQL). Ignore the error and look at the output. It returns 3 lines and works properly. Now write some extra code and modify it as you want. The error does not seem important and I have no idea what the reason is.
declare
--
txt nvarchar2(1000):='Before continuing, turn off the top title display without changing its definition:';
charc nvarchar2(1):='';
TYPE txtArrTyp IS VARRAY(1000) OF NVARCHAR2(1);
txtArr txtArrTyp :=txtArrTyp();
--
str nvarchar2(35):='';
cntr number:=0;
j number:=0;
lent number:=0;
begin
--
lent:=LENGTHB(txt);
--
for i In 1 ..lent
loop
if(txt is null )then
dbms_output.put_line('SHIT');
end if;
charc := SUBSTR(txt,i,1);
txtArr.extend;
txtArr(i):=charc;
end loop;
--
While(j>=1 or j<=lent)
loop
j:=j+1;
cntr :=cntr+1;
if(cntr = 35) then
while(txtArr(j)<>' ')
loop
j:=j-1;
end loop;
str:=substr(str,0,j);
dbms_output.put_line(str);
str:=null;
cntr:=0;
end if;
str := str || txtArr(j);
end loop;
dbms_output.put_line(str);
end;

Batch takes a part from the file name and create a folders with this part

I have files with names like this:
414_gtmlk_videos_Mas_147852_hty1147.xls
414_gtmlk_videos_Mas_P147852_hty1147.txt
I want to creat a job to check the filenames and take the part after Mas in the file name (147852-P147852)
and create a folders with this part name (the folder name should be: 147852-P147852).
And finally move each file to his folder.
Batch takes a part from the file name and create a folders with this part i have files with names like this:
414_gtmlk_videos_Mas_147852_hty1147.xls
414_gtmlk_videos_Mas_P147852_hty1147.txt (the folder name will be
here:147852-P147852)
Here's a way to do this with a Batch Script since you have this tagged as a batch-file in your question. Just set your source directory accordingly and the rest should just work based on the detail you provided and my understanding.
I used a simple batch FOR /F loop incorporating MD with IF conditions. I used the underbar characters as the delimiter and set the token to 5 to make this work.
#ECHO ON
SET Src=C:\Folder\Path
FOR /F "TOKENS=5 DELIMS=_" %%F IN ('DIR /B /A-D "%Src%\*.txt"') DO (
IF NOT EXIST "%Src%\%%~F-P%%~F" MD "%Src%\%%~F-P%%~F"
IF EXIST "%Src%\*%%~F*P%%~F*.txt" MOVE /Y "%Src%\*%%~F*P%%~F*.txt" "%Src%\%%~F-P%%~F"
)
GOTO EOF
Further Resources
FOR /F
IF
MD
FOR /?
delims=xxx - specifies a delimiter set. This replaces the
default delimiter set of space and tab.
tokens=x,y,m-n - specifies which tokens from each line are to
be passed to the for body for each iteration.
This will cause additional variable names to
be allocated. The m-n form is a range,
specifying the mth through the nth tokens. If
the last character in the tokens= string is an
asterisk, then an additional variable is
allocated and receives the remaining text on
the line after the last token parsed.
I have some C# code below. The first part does the following:
Gets paths
Get names of file
Modify full paths to get "147852" part, between __Mas_ and last _
string pathToGetFile = #"C:\\";
string[] filePaths = System.IO.Directory.GetFiles(pathToGetFile +#"\\", "*_Mas_*");
string[] fullName = new string[filePaths.Length];
for (int i = 0; i < filePaths.Length; i++)
{
fullName[i] = filePaths[i].Substring(filePaths[i].LastIndexOf("\\") + 1);
filePaths[i] = filePaths[i].Substring(filePaths[i].LastIndexOf("_Mas_") + 5);
int l = filePaths[i].IndexOf("_");
filePaths[i] = filePaths[i].Substring(0, l);
Now you can create folders with yours names
filePaths is now like that: 147852, P147852
if (!Directory.Exists(#"C:\" + filePaths[i]))
System.IO.Directory.CreateDirectory(#"C:\" + filePaths[i]);
}
Now just move files to new directories
for (int i = 0; i < filePaths.Length; i++)
{
string sourceFile = System.IO.Path.Combine(pathToGetFile, fullName[i]);
string destFile = System.IO.Path.Combine(#"C:\" + filePaths[i], #"C:\" + filePaths[i] + "\\" + fullName[i]);
File.Copy(sourceFile,destFile,true);
}
Now, what happens
Files:
C:\414_gtmlk_videos_Mas_147852_hty1147.xls
C:\414_gtmlk_videos_Mas_P147852_hty1147.txt
They will be copied according to the:
C:\147852\
C:\P147852\

Execute oracle DDL script with functions from c# code

I've some integration tests written in C# code using a Oracle Database. The test project has a CreateDatabase.sql that contains the DDL to create the entire database in each test execution.
When I had only sequences and tables, I was splitting the content of this file in ";" char and executing each create statement separately, but now I've some functions and their statements contains some ; chars in it, so I can't use this approach anymore.
I've checked on .NET / Oracle: How to execute a script with DDL statements programmatically question, but it did not help.
1) If I try to execute the entire file content in a single OracleCommand, I get an error ORA-00911: invalid character because of the ; chars
2) If I try to wrap the file content in a "begin {0} end;" I get an error PLS-00103: Encountered the symbol "CREATE" when expecting one of the following: ...
3) I could try to parse the SQL file and put each statement inside a EXECUTE IMMEDIATE, but it will be harder...
Is there another option?
I'm using the Oracle.DataAccess version 4.112.3.0 to execute the commands.
EDIT
#kevinsky ask for a script, here it is a simplified example... the entire script create hundreds of objects...
CREATE SEQUENCE SQ_ARAN_SQ_ARQUIVO_ANEXO;
CREATE OR REPLACE FUNCTION UFC_SPSW_DISP_COMPOSICAO(p_id_composicao IN NUMBER) RETURN NUMBER
IS
retorno NUMBER:= 0;
numeroItens NUMBER;
temDefinicao BOOLEAN := false;
CURSOR item_cur is
select idc.itdc_sq_item_definicao_composi, idc.insu_sq_insumo, idc.comp_sq_composicao, idc.itdc_nr_coeficiente, idc.COMP_DS_COMPOSICAO, idc.comp_sq_composicao_pai
from item_definicao_composicao idc;
BEGIN
FOR item_rec IN item_cur LOOP
temDefinicao := true;
IF (item_rec.itdc_nr_coeficiente is null) THEN
RETURN null;
ELSE
IF (item_rec.insu_sq_insumo is null) THEN
numeroItens := UFC_SPSW_DISP_COMPOSICAO(nvl(item_rec.comp_sq_composicao_pai, item_rec.comp_sq_composicao));
else
retorno := retorno + 1;
END IF;
END IF;
END LOOP;
IF (temDefinicao = false) THEN
RETURN 0;
END IF;
RETURN retorno;
END;
CREATE SEQUENCE SQ_CALC_SQ_CALCULO;
I had the same prolem and solved.
Use both 'BEGIN END' and 'EXECUTE IMMEDIATE'.
This is my test (success case)
begin
EXECUTE IMMEDIATE 'create or replace procedure SP_JHKIM2 IS
begin
dbms_output.put_line(''ABC'');
end;';
end;
Here is an idea. Make the front slash / (alone on a separate line) your new standard way of terminating every statement in your script instead of relying on the semi colon. For instance, your sample script could become:
CREATE SEQUENCE SQ_ARAN_SQ_ARQUIVO_ANEXO
/
CREATE OR REPLACE FUNCTION UFC_SPSW_DISP_COMPOSICAO(p_id_composicao IN NUMBER) RETURN NUMBER
IS
retorno NUMBER:= 0;
numeroItens NUMBER;
temDefinicao BOOLEAN := false;
CURSOR item_cur is
select idc.itdc_sq_item_definicao_composi, idc.insu_sq_insumo, idc.comp_sq_composicao, idc.itdc_nr_coeficiente, idc.COMP_DS_COMPOSICAO, idc.comp_sq_composicao_pai
from item_definicao_composicao idc;
BEGIN
FOR item_rec IN item_cur LOOP
temDefinicao := true;
IF (item_rec.itdc_nr_coeficiente is null) THEN
RETURN null;
ELSE
IF (item_rec.insu_sq_insumo is null) THEN
numeroItens := UFC_SPSW_DISP_COMPOSICAO(nvl(item_rec.comp_sq_composicao_pai, item_rec.comp_sq_composicao));
else
retorno := retorno + 1;
END IF;
END IF;
END LOOP;
IF (temDefinicao = false) THEN
RETURN 0;
END IF;
RETURN retorno;
END;
/
CREATE SEQUENCE SQ_CALC_SQ_CALCULO
/
By using the /, your script remains perfectly valid if you wish to run it using SQL*Plus. But it now has the advantage that it becomes trivial to parse by statement in C# so that you can execute each statement separately without the semi colon problems.
I've used this technique in the past and it has worked well.
(Relevant reading in case you are not familiar with the use of the slash in Oracle SQL scripts: When do I need to use a semicolon vs a slash in Oracle SQL?.)
I split only on ; that was followed by some reserved words (or in the end of the file) using regex lookahead assertion.
Ex:
var statements = Regex.Split(
fileContent,
#"\s*;\s*(?=(?:CREATE|ALTER|DROP|RENAME|TRUNCATE)\s|\s*$)",
RegexOptions.IgnoreCase);

How to use replace with tricky characters in C#?

I am trying to replace within a string
<?xml version="1.0" encoding="UTF-8"?>
<response success="true">
<output><![CDATA[
And
]]></output>
</response>
with nothing.
The problem I am running into is the characters <> and " characters are interacting within the replace. Meaning, it's not reading those lines as a full string all together as one but breaking the string when it comes to a <> or ". Here is what I have but I know this isn't right:
String responseString = reader.ReadToEnd();
responseString.Replace(#"<<?xml version=""1.0"" encoding=""UTF-8""?><response success=""true""><output><![CDATA[[", "");
responseString.Replace(#"]]\></output\></response\>", "");
What would be the correct code to get the replace to see these lines as just a string?
A string will never change. The Replace method works as follows:
string x = "AAA";
string y = x.Replace("A", "B");
//x == "AAA", y == "BBB"
However, the real problem is how you handle the XML response data.
You should reconsider your approach of handling incoming XML by string replacement. Just get the CDATA content using the standard XML library. It's as easy as this:
using System.Xml.Linq;
...
XDocument doc = XDocument.Load(reader);
var responseString = doc.Descendants("output").First().Value;
The CDATA will already be removed. This tutorial will teach more about working with XML documents in C#.
Given your document structure, you could simply say something like this:
string response = #"<?xml version=""1.0"" encoding=""UTF-8""?>"
+ #"<response success=""true"">"
+ #" <output><![CDATA["
+ #"The output is some arbitrary text and it may be found here."
+ "]]></output>"
+ "</response>"
;
XmlDocument document = new XmlDocument() ;
document.LoadXml( response ) ;
bool success ;
bool.TryParse( document.DocumentElement.GetAttribute("success"), out success) ;
string content = document.DocumentElement.InnerText ;
Console.WriteLine( "The response indicated {0}." , success ? "success" : "failure" ) ;
Console.WriteLine( "response content: {0}" , content ) ;
And see the expected results on the console:
The response indicated success.
response content: The output is some arbitrary text and it may be found here.
If your XML document is a wee bit more complex, you can easily select the desired node(s) using an XPath query, thus:
string content = document.SelectSingleNode( #"/response/output" ).InnerText;

Categories