Is there a limit on the length of the formula?
If I use:
string form = "=ЕСЛИ(ЛЕВСИМВ(C2;3)=\"101\";\"цту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"102\";\"сзту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"103\";\"юту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"104\";\"пту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"105\";\"уту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"106\";\"сту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"107\";\"двту\";\"скту\")))))))";
xlWorkSheet.Range["D2"].FormulaLocal = form;
That's all okay.
But if I use:
string form = "=ЕСЛИ(ЛЕВСИМВ(C2;3)=\"101\";\"цту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"102\";\"сзту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"103\";\"юту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"104\";\"пту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"105\";\"уту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"106\";\"сту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"107\";\"двту\";ЕСЛИ(ЛЕВСИМВ(C2;3)=\"108\";\"скту\";\"fuck\"))))))))";
xlWorkSheet.Range["D2"].FormulaLocal = form;
That error takes off:
HRESULT: 0x800A03EC
It seems there is a limit to the length of string written to .FormulaLocal (and other forms of .Formula)
By experimentation in VBA, it apears to be 258 characters. I can find some references on various blogs to this suggesting a limit of about 256, but can find an official source.
Interestingly, this limit doesn't apply if the string doesn't include a = prefix.
Tested on Excel 2010.
For completness, here's the test code
Sub Demo()
Dim s As String, ss As String, f As String
Dim r As Range
Dim i As Long
Set r = [A20]
s = "=""This is a test of string length: "
ss = "z"
On Error GoTo EH
For i = 1 To 350
f = s & ss & """"
Debug.Print ""
Debug.Print Len(f)
r.FormulaLocal = f
Debug.Print "OK"
ss = "z" & ss
Next
Exit Sub
EH:
Debug.Print "Fail"
End Sub
And the last few lines of the result
257
OK
258
OK
259
Fail
It gets weirder: this code works
Sub Demo()
Dim s As String, ss As String, f As String
Dim r As Range
Dim i As Long
Set r = [A20]
s = "IF(A1=1,AAA999,FALSE)"
ss = "z"
r.FormulaLocal = "=" & s
On Error GoTo EH
For i = 1 To 60
Debug.Print ""
Debug.Print Len(r.FormulaLocal)
r.FormulaLocal = Replace(r.FormulaLocal, "AAA999", s)
Debug.Print "OK"
Debug.Print Len(r.FormulaLocal)
Next
Exit Sub
EH:
Debug.Print "Fail"
End Sub
What this does is builds up the formula up in sections, each as a valid Formula in its own right. Fails at about 1000 characters (1024 maybe).
Check the following page for the Excel limits:
https://support.office.com/en-nz/article/Excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3
In particular, it says:
Length of formula contents - 8,192 characters
Internal length of formula - 16,384 bytes
Related
I'm trying to convert the Bodies from Parts to Parts in a Product in CATIA. I found a VBA script that works fine, but I have to translate it to C#.
The VBA script is the following:
Sub GreateProductsFromBodies_SelectAllBodies()
On Error Resume Next
Set CATIA = GetObject(, "CATIA.Application")
'Declare variables
Dim oPartDoc As PartDocument
Dim oPart As Part
Dim oProductDoc As ProductDocument
Dim oProduct As Product
'Create a new ProductDoc and rename it's PartNumber equals to Partdoc's PartNumber
Set oPartDoc = CATIA.ActiveDocument
Set oProductDoc = CATIA.Documents.Add("Product")
oProductDoc.Product.PartNumber = oPartDoc.Product.PartNumber
'Arrange windows use "Title Vertically" ,then active window contain Partdoc
CATIA.Windows.Arrange catArrangeTiledVertical
CATIA.Windows.Item(1).Activate
'Check the Body's name use "For ... Next"loop . If Body's name duplicate,then rename.
Dim j As Integer, k As Integer
For j = 1 To oPartDoc.Part.Bodies.Count
For k = 1 To oPartDoc.Part.Bodies.Count
If oPartDoc.Part.Bodies.Item(j).name = oPartDoc.Part.Bodies.Item(k).name And j <> k Then
oPartDoc.Part.Bodies.Item(j).name = oPartDoc.Part.Bodies.Item(k).name & "_Rename_" & j
End If
Next
Next
'Copy Bodies from PartDocument
Dim i As Integer, ProductPN As String, FinalProductPN As String
For i = 1 To oPartDoc.Part.Bodies.Count
With oPartDoc.Selection
.Clear
.Add oPartDoc.Part.Bodies.Item(i)
.Copy
.Clear
End With
'Modify the Product's PartNumber,replace "\" and "."to "_" ,then delete Space
ProductPN = oPartDoc.Part.Bodies.Item(i).name
If Right(ProductPN, 1) = "\" Then
ProductPN = Left(ProductPN, Len(ProductPN) - 1)
End If
FinalProductPN = Replace(Replace(Replace(ProductPN, "\", "_"), ".", "_"), " ", "") 'Replace "\" and "."to "_",Delete Space
'Paste Body in Product's Part as Result
Set oProduct = oProductDoc.Product.Products.AddNewComponent("Part", FinalProductPN) 'Add Part
With oProductDoc.Selection
.Clear
.Add oProductDoc.Product.Products.Item(i).ReferenceProduct.Parent.Part
.PasteSpecial "CATPrtResultWithOutLink"
.Clear
End With
oProductDoc.Product.Products.Item(i).ReferenceProduct.Parent.Part.Update
Next
'Use Msgbox to echo the complete flag
MsgBox "All the select Bodies had been created as a PartDocument successfully !" & Chr(13) & _
">>> The Partdocument's Bodies's count : " & oPartDoc.Part.Bodies.Count & Chr(13) & _
">>> The ProductDocument's PartDocument's count : " & oProductDoc.Product.Products.Count, _
vbOKOnly + vbInformation, "#LSY >>> CATIAVBAMacro of Part to Product >>> Run Result"
End Sub
I translated every line, except the line:
oProductDoc.Selection.Add oProductDoc.Product.Products.Item(i).ReferenceProduct.Parent.Part
I found no corresponding property in C#, cause the last property Part is missing in C#.
I wrote:
oProductDoc.Selection.Add(oProductDoc.Product.Products.Item(i).ReferenceProduct.Parent.??????);
I'm very thankfull for every help!
I solved it, if someone else is in this situation:
I had to cast the line as PartDocument, whish gives me the needed .Part Property!
Before the selection:
PartDocument partDoc = oProductDoc.Product.Products.Item(i).ReferenceProduct.Parent as PartDocument;
And in the required line:
oProductDoc.Selection.Add(partDoc.Part);
Hi All below code is how to compare contents in two text file and is work fine for record in files, but my issue when files have a lot line ( 80000 up) my code work very very slow and i cannot accept it. please kindly give me some idea
Public Class Form1
Const TEST1 = "D:\a.txt"
Const TEST2 = "D:\b.txt"
Public file1 As New Dictionary(Of String, String)
Public file2 As New Dictionary(Of String, String)
Public text1 As String()
Public i As Integer
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'Declare two dictionaries. The key for each will be the text from the input line up to,
'but not including the first ",". The valus for each will be the entire input line.
'Dim file1 As New Dictionary(Of String, String)
'Dim file2 As New Dictionary(Of String, String)
'Dim text1 As String()
For Each line As String In System.IO.File.ReadAllLines(TEST1)
Dim part() As String = line.Split(",")
file1.Add(part(0), line)
Next
For Each line As String In System.IO.File.ReadAllLines(TEST2)
Dim part() As String = line.Split(",")
file2.Add(part(0), line)
Next
' AddText("The following lines from " & TEST2 & " are also in " & TEST1)
For Each key As String In file1.Keys
If file2.ContainsKey(key) Then
TextBox1.Text &= (file1(key)) & vbCrLf
MsgBox(file2(key))
Label1.Text = file1(key)
Else
TextBox2.Text &= (file1(key)) & vbCrLf
End If
Next
text1 = TextBox1.Lines
IO.File.WriteAllLines("D:\Same.txt", text1)
text1 = TextBox2.Lines
IO.File.WriteAllLines("D:\Differrent.txt", text1)
End Sub
The first thing I would change is the use of a Dictionary. I would use an Hashset.
See HashSet versus Dictionary
Then I would change the ReadAllLines loop. The ReadAllLines loads every line in memory before starting the loop, while ReadLines doesn't read all lines but you can start to work on your line immediately.
See What's the fastest way to read a text file line-by-line?
The third point is switching the order of the files read. First read the TEST2 file then the TEST1. This because while you load TEST1 lines you could immediately check if the file2 Hashset contains the key and Add the found line in a list of found strings while the line not found in a list of not found strings.
Dim TEST1 = "D:\temp\test3.txt"
Dim TEST2 = "D:\temp\test6.txt"
Dim file2Keys As New Hashset(Of String)
For Each line As String In System.IO.File.ReadLines(TEST2)
Dim parts = line.Split(",")
file2Keys.Add(parts(0))
Next
Dim listFound As New List(Of String)()
Dim listNFound= New List(Of String)()
For Each line As String In System.IO.File.ReadLines(TEST1)
Dim parts = line.Split(",")
If file2Keys.Contains(parts(0)) Then
listFound.Add(line)
Else
listNFound.Add(line)
End If
Next
IO.File.WriteAllText("D:\temp\Same.txt", String.Join(Environment.NewLine, listFound.ToArray()))
IO.File.WriteAllText("D:\temp\Differrent.txt", String.Join(Environment.NewLine, listNFound.ToArray()))
288007 327920 374740 000368 044575 082865 680798
717374 755879 811106 855460 920577 953515 996819 ......
I have a string containing thousands of 6-digit numbers and I want to extract the Nth numbers after Nth number with the help of regular expression.
Let say I need to extract Three numbers after the 4th number then The result should be 044575 082865 680798.
another example If I need to extract 2 numbers after the 10th number then the result should be 855460 920577.
I don't know is this possible with regex, I think FOR EACH statement may be use in my case.
I am only able to extract each six digits number with the code below.
Dim NumberMatchCollection As MatchCollection = Regex.Matches("String containing numbers", "(?<!\d)\d{6}(?!\d)")
For Each NumberMatch As Match In NumberMatchCollection
Dim ItemNumber As String = NumberMatch.Value
Next
Edited:
I can not guarantee that every separator character will be a single space, a double space, a tab or something else. I can just guarantee that the number length always will be 6 which will be separated by space(s) or tab(s).
Wouldn't this be simpler using maths?
Three numbers after the 4th number, is chars (7 * 4) + (7 * 3)
To expand on my comment. This assume that the actual data are divided equaly.
If each number have 6 digits with a space in between. Then the position of the 4th number will be (6+1)*4 and if you want 3 numbers than you just need to fetch (6+1)*3 amount of characters.
Dim str As String
str = "288007 327920 374740 000368 044575 082865 680798 717374 755879 811106 855460 920577 953515 996819"
Dim startingNumber As Integer = 4
Dim amountToFetch As Integer = 3
' 7 = [size of each number] + [delimiter length]
' 7 = 6 + 1
Console.WriteLine(str.Substring(7 * startingNumber, 7 * amountToFetch))
Console.ReadLine()
If you would like a regex and c# solution, the following code does the 3 numbers after 4th number example.
var st = #"288007 327920 374740 000368 044575 082865 680798
717374 755879 811106 855460 920577 953515 996819";
var pattern = #"^(\d+\s+){4}((?<x>\d+)\s+){3}";
var matches = Regex.Matches(st,pattern,RegexOptions.Singleline);
foreach (Capture m in matches[0].Groups["x"].Captures)
Console.WriteLine("value={0}", m.Value);
(Edit: removed one group per comment below)
You can .Split() the string and use LINQ extension methods on the resulting array:
// some test data...
var rand = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 10000; i++)
{
sb.Append(i.ToString("000000") + ((rand.Next(5)==1) ? " ": "\t"));
}
string s = sb.ToString();
string portion = string.Join(" ", s.Split(new [] {' ', '\t'}, StringSplitOptions.RemoveEmptyEntries).Skip(10).Take(3));
Console.WriteLine(portion); // outputs "000011 000012 000013"
Note: for the first number you would .Skip(0).
But if your string is in the rigid format you show (asuming the variable numbers of spaces are typos, thanks #ErikE), Coenraad's method of calculating where the start of the required string is and how many characters to take would be more efficient. I'll leave it to Coenraad to expand on that answer as it would not be fair to possibly take the points.
I tried and tried to make the regex method be consistently fast, but I found it depended strongly on which numbers you want to retrieve:
For anyone wanting to test that, I put a default Chart on a Form and used this code:
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Windows.Forms.DataVisualization
Imports System.Windows.Forms.DataVisualization.Charting
Public Class Form1
Sub DoStuff()
Dim ser1 As New Series With {.Name = "String.Split"}
Dim ser2 As New Series With {.Name = "RegEx"}
Dim sb As New StringBuilder()
For i As Integer = 1 To 10000
sb.Append(i.ToString("000000") + " ")
Next
Dim s As String = sb.ToString()
Dim sw As New Stopwatch()
Dim itemsToTake As Integer = 50
For firstItem = 1 To 9000 Step 100
sw.Restart()
Dim portion As String = String.Join(" ", s.Split({" "c}, StringSplitOptions.RemoveEmptyEntries).Skip(firstItem - 1).Take(itemsToTake))
sw.Stop()
ser1.Points.AddXY(firstItem -1, sw.ElapsedTicks)
Dim pattern = "^(?:\d+\s+){" + (firstItem - 1).ToString() + "}((\d+)\s+){" + itemsToTake.ToString() + "}"
Dim re = New Regex(pattern)
sw.Restart()
Dim matches = re.Matches(s)
Dim cs = matches(0).Groups(0).Captures
sw.Stop()
ser2.Points.AddXY(firstItem - 1, sw.ElapsedTicks)
Next
Chart1.Series.Clear()
Chart1.Series.Add(ser1)
Chart1.Series(0).ChartType = SeriesChartType.Line
Chart1.Series.Add(ser2)
Chart1.Series(1).ChartType = SeriesChartType.Line
Chart1.ChartAreas(0).AxisX.IsMarginVisible = False
Chart1.ChartAreas(0).AxisX.Title = "First item to retrieve"
Chart1.ChartAreas(0).AxisY.Title = "Time taken"
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
DoStuff()
End Sub
End Class
What is the maximum string length of PrintArea in Excel 2003 and 2010?
I have a PrintArea string length of 677.
This throws an error in Excel 2003, but not in 2010, so I'd like to know what the maximum string length is in both versions as well as 2007.
The limit in 2003 and 2007 is 255 characters.
I don't have a copy of 2010 to test, but you can use this VBA code to test it easily. Just run the macro and after it crashes, go to Debug, and check the value of i. One less than that will be the maximum string length.
Sub PrintRangeTest()
Dim i As Integer
Dim j As Integer
Dim newName As String
newName = ""
Dim rng As Range
For i = 1 To 100000 //some arbitrarily large number
newName = ""
For j = 1 To i
newName = newName & "a"
Next
Set rng = ActiveSheet.Range(Cells(1, 1), Cells(i, i))
rng.Name = newName
ActiveSheet.PageSetup.PrintArea = rng
Next
End Sub
How can I detect what port (Ne01:, Ne02:, Ne99: etc) the printer is on?
Computers (WinXP) here at BigCorp have Adobe Acrobat (version 7.0 Pro) installed which gives a virtual printer named "Adobe PDF". If you print an Excel (2003) workbook to pdf while recording a macro, the printer's full name is "Adobe PDF on Nexx:" where xx is a double digit.... and differs depending on what computer you try.
I have written a C# console app using the Excel.Interop (I strongly discourage anyone else from starting down this road to hell) that opens a series of spreadsheets. It runs a macro in each one, saves, prints is as a pdf, then moves the pdf to a reports folder on a shared drive.
The problem I face is that every install of Acrobat seems to pick a random port number for the PDF printer... and I can't figure out how to get it.
So far I have tried using the Win32_Printer class like so
var searcher = new ManagementObjectSearcher( #"SELECT * FROM Win32_Printer" );
foreach ( ManagementObject printer in searcher.Get() )
{
if ( Regex.IsMatch( printer["Name"].ToString(), #"(adobe|pdf)", RegexOptions.IgnoreCase ) )
{
//printer["Name"]; => "Adobe PDF"
//printer["PortName"] => "my documents/*.pdf"
foreach ( PropertyData pd in printer.Properties )
{
Console.WriteLine(string.Format("{0}, {1}", pd.Name, pd.Value));
}
break;
}
}
I also poked around in the System.Drawing.Printing class. The PrinterSettings.InstalledPrinters will give you the name of the printer "Adobe PDF" but I can't figure out how to get the port info.
If I pass just "Adobe PDF" to the excel interop PrintOut() method it sometimes works and sometimes fails with "Document failed to print"... I cannot figure out why.
If I pass a hardcoded "Adobe PDF on Ne0x:" with an appropriate x value it works every time.
If I try every possible variation, Excel helpfully prints to the default printer. I do not have the option of changing the default printer (security policy restriction)
Can anyone point me to code that correctly pulls the printer port?
Here's what I ended up doing
using Microsoft.Win32;
...
var devices = Registry.CurrentUser.OpenSubKey( #"Software\Microsoft\Windows NT\CurrentVersion\Devices" ); //Read-accessible even when using a locked-down account
string printerName = "Adobe PDF";
try
{
foreach ( string name in devices.GetValueNames() )
{
if ( Regex.IsMatch( name, printerName, RegexOptions.IgnoreCase ) )
{
var value = (String)devices.GetValue( name );
var port = Regex.Match( value, #"(Ne\d+:)", RegexOptions.IgnoreCase ).Value;
return printerName + " on " + port;
}
}
}
catch
{
throw;
}
Last time I used Acrobat it always used to install itself on LPT1: thus avoiding the problem. But I think you have to grovel around in the registry, HKCU\Software\Microsoft\Windows NT\CurrentVersion\Devices has them.
As you discovered you have to query the registry, the is the way I used [in VBA], which I got from Chip Pearson's great Excel site:
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' modListPrinters
' By Chip Pearson, chip#cpearson.com www.cpearson.com
' Created 22-Sept-2012
' This provides a function named GetPrinterFullNames that
' returns a String array, each element of which is the name
' of a printer installed on the machine.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Const HKEY_CURRENT_USER As Long = &H80000001
Private Const HKCU = HKEY_CURRENT_USER
Private Const KEY_QUERY_VALUE = &H1&
Private Const ERROR_NO_MORE_ITEMS = 259&
Private Const ERROR_MORE_DATA = 234
Private Const REG_SZ = 1
Private Declare Function RegOpenKeyEx Lib "advapi32" _
Alias "RegOpenKeyExA" ( _
ByVal hKey As Long, _
ByVal lpSubKey As String, _
ByVal ulOptions As Long, _
ByVal samDesired As Long, _
phkResult As Long) As Long
Private Declare Function RegEnumValue Lib "ADVAPI32.DLL" _
Alias "RegEnumValueA" ( _
ByVal hKey As Long, _
ByVal dwIndex As Long, _
ByVal lpValueName As String, _
lpcbValueName As Long, _
ByVal lpReserved As Long, _
lpType As Long, _
lpData As Byte, _
lpcbData As Long) As Long
Private Declare Function RegCloseKey Lib "ADVAPI32.DLL" ( _
ByVal hKey As Long) As Long
Public Function GetPrinterFullNames() As String()
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' GetPrinterFullNames
' By Chip Pearson, chip#cpearson.com, www.cpearson.com
' Returns an array of printer names, where each printer name
' is the device name followed by the port name. The value can
' be used to assign a printer to the ActivePrinter property of
' the Application object. Note that setting the ActivePrinter
' changes the default printer for Excel but does not change
' the Windows default printer.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim Printers() As String ' array of names to be returned
Dim PNdx As Long ' index into Printers()
Dim hKey As Long ' registry key handle
Dim Res As Long ' result of API calls
Dim Ndx As Long ' index for RegEnumValue
Dim ValueName As String ' name of each value in the printer key
Dim ValueNameLen As Long ' length of ValueName
Dim DataType As Long ' registry value data type
Dim ValueValue() As Byte ' byte array of registry value value
Dim ValueValueS As String ' ValueValue converted to String
Dim CommaPos As Long ' position of comma character in ValueValue
Dim ColonPos As Long ' position of colon character in ValueValue
Dim M As Long ' string index
' registry key in HCKU listing printers
Const PRINTER_KEY = "Software\Microsoft\Windows NT\CurrentVersion\Devices"
PNdx = 0
Ndx = 0
' assume printer name is less than 256 characters
ValueName = String$(256, Chr(0))
ValueNameLen = 255
' assume the port name is less than 1000 characters
ReDim ValueValue(0 To 999)
' assume there are less than 1000 printers installed
ReDim Printers(1 To 1000)
' open the key whose values enumerate installed printers
Res = RegOpenKeyEx(HKCU, PRINTER_KEY, 0&, _
KEY_QUERY_VALUE, hKey)
' start enumeration loop of printers
Res = RegEnumValue(hKey, Ndx, ValueName, _
ValueNameLen, 0&, DataType, ValueValue(0), 1000)
' loop until all values have been enumerated
Do Until Res = ERROR_NO_MORE_ITEMS
M = InStr(1, ValueName, Chr(0))
If M > 1 Then
' clean up the ValueName
ValueName = Left(ValueName, M - 1)
End If
' find position of a comma and colon in the port name
CommaPos = InStr(1, ValueValue, ",")
ColonPos = InStr(1, ValueValue, ":")
' ValueValue byte array to ValueValueS string
On Error Resume Next
ValueValueS = Mid(ValueValue, CommaPos + 1, ColonPos - CommaPos)
On Error GoTo 0
' next slot in Printers
PNdx = PNdx + 1
Printers(PNdx) = ValueName & " on " & ValueValueS
' reset some variables
ValueName = String(255, Chr(0))
ValueNameLen = 255
ReDim ValueValue(0 To 999)
ValueValueS = vbNullString
' tell RegEnumValue to get the next registry value
Ndx = Ndx + 1
' get the next printer
Res = RegEnumValue(hKey, Ndx, ValueName, ValueNameLen, _
0&, DataType, ValueValue(0), 1000)
' test for error
If (Res <> 0) And (Res <> ERROR_MORE_DATA) Then
Exit Do
End If
Loop
' shrink Printers down to used size
ReDim Preserve Printers(1 To PNdx)
Res = RegCloseKey(hKey)
' Return the result array
GetPrinterFullNames = Printers
End Function
I then use this function to get the PDF printer name:
Public Function FindPDFPrinter() As String
'this function finds the exact printer name for the Adobe PDF printer
Dim Printers() As String
Dim N As Integer
FindPDFPrinter = ""
Printers = GetPrinterFullNames()
For N = LBound(Printers) To UBound(Printers)
If InStr(1, Printers(N), "PDF") Then
FindPDFPrinter = Printers(N)
End If
Next N
End Function
You then set Application.ActivePrinter to that String.
And if you really just need the port, you can pull it off of the end of the string.