Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 55 additions & 4 deletions main/HSSF/UserModel/HSSFName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ namespace NPOI.HSSF.UserModel
using NPOI.HSSF.Model;
using NPOI.SS.Formula.PTG;
using System.Text.RegularExpressions;
using NPOI.SS;
using NPOI.SS.Util;


/// <summary>
Expand Down Expand Up @@ -124,6 +126,27 @@ public String NameName
}
}
}
/**
* https://support.office.com/en-us/article/Define-and-use-names-in-formulas-4D0F13AC-53B7-422E-AFD2-ABD7FF379C64#bmsyntax_rules_for_names
*
* Valid characters:
* First character: { letter | underscore | backslash }
* Remaining characters: { letter | number | period | underscore }
*
* Cell shorthand: cannot be { "C" | "c" | "R" | "r" }
*
* Cell references disallowed: cannot be a cell reference $A$1 or R1C1
*
* Spaces are not valid (follows from valid characters above)
*
* Name length: (XSSF-specific?) 255 characters maximum
*
* Case sensitivity: all names are case-insensitive
*
* Uniqueness: must be unique (for names with the same scope)
*
* @param name
*/
private void ValidateName(String name)
{
/* equivalent to:
Expand All @@ -140,28 +163,56 @@ thus we are stuck with Character.isLetter (for now).
throw new ArgumentException("Name cannot be blank");
}

if (name.Length > 255)
{
throw new ArgumentException("Invalid name: '" + name + "': cannot exceed 255 characters in length");
}
if (name.Equals("R", StringComparison.OrdinalIgnoreCase) || name.Equals("C", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Invalid name: '" + name + "': cannot be special shorthand R or C");
}

// is first character valid?
char c = name[0];
String allowedSymbols = "_";
String allowedSymbols = "_\\";
bool characterIsValid = (char.IsLetter(c) || allowedSymbols.IndexOf(c) != -1);
if (!characterIsValid)
{
throw new ArgumentException("Invalid name: '" + name + "': first character must be underscore or a letter");
}

// are all other characters valid?
allowedSymbols = "_\\"; //backslashes needed for unicode escape
allowedSymbols = "_.\\"; //backslashes needed for unicode escape
foreach (char ch in name.ToCharArray())
{
characterIsValid = (char.IsLetterOrDigit(ch) || allowedSymbols.IndexOf(ch) != -1);
if (!characterIsValid)
{
throw new ArgumentException("Invalid name: '" + name + "'");
throw new ArgumentException("Invalid name: '" + name + "': name must be letter, digit, period, or underscore");
}
}

// Is the name a valid $A$1 cell reference
// Because $, :, and ! are disallowed characters, A1-style references become just a letter-number combination
if (Regex.IsMatch(name, "[A-Za-z]+\\d+"))
{
string col = Regex.Replace(name, "\\d", "");
string row = Regex.Replace(name, "[A-Za-z]", "");
if (CellReference.CellReferenceIsWithinRange(col, row, SpreadsheetVersion.EXCEL97))
{
throw new ArgumentException("Invalid name: '" + name + "': cannot be $A$1-style cell reference");
}
}

// Is the name a valid R1C1 cell reference?
if (Regex.IsMatch(name,"[Rr]\\d+[Cc]\\d+"))
{
throw new ArgumentException("Invalid name: '" + name + "': cannot be R1C1-style cell reference");
}
}

public String RefersToFormula

public string RefersToFormula
{
get
{
Expand Down
10 changes: 9 additions & 1 deletion main/Util/TempFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,18 @@ public static string GetTempFilePath(String prefix, String suffix)
{
dir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "poifiles")).FullName;
}

Random rnd = new Random(DateTime.Now.Millisecond);
rnd.Next();
Thread.Sleep(10);
//return prefix + rnd.Next() + suffix;
return Path.Combine(dir, prefix + rnd.Next() + suffix);
string path = Path.Combine(dir, prefix + rnd.Next() + suffix);
while(File.Exists(path))
{
Thread.Sleep(10);
path = Path.Combine(dir, prefix + rnd.Next() + suffix);
}
return path;
}
}
}
59 changes: 59 additions & 0 deletions ooxml/POIXMLDocumentPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,65 @@ public POIXMLDocumentPart CreateRelationship(POIXMLRelation descriptor, POIXMLFa
return CreateRelationship(descriptor, factory, idx, false).DocumentPart;
}

/**
* Identifies the next available part number for a part of the given type,
* if possible, otherwise -1 if none are available.
* The found (valid) index can then be safely given to
* {@link #createRelationship(POIXMLRelation, POIXMLFactory, int)} or
* {@link #createRelationship(POIXMLRelation, POIXMLFactory, int, boolean)}
* without naming clashes.
* If parts with other types are already claiming a name for this relationship
* type (eg a {@link XSSFRelation#CHART} using the drawing part namespace
* normally used by {@link XSSFRelation#DRAWINGS}), those will be considered
* when finding the next spare number.
*
* @param descriptor The relationship type to find the part number for
* @param minIdx The minimum free index to assign, use -1 for any
* @return The next free part number, or -1 if none available
*/
protected internal int GetNextPartNumber(POIXMLRelation descriptor, int minIdx)
{
OPCPackage pkg = packagePart.Package;

try
{
if (descriptor.DefaultFileName.Equals(descriptor.GetFileName(9999)))
{
// Non-index based, check if default is free
PackagePartName ppName = PackagingUriHelper.CreatePartName(descriptor.DefaultFileName);
if (pkg.ContainPart(ppName))
{
// Default name already taken, not index based, nothing free
return -1;
}
else
{
// Default name free
return 0;
}
}

// Default to searching from 1, unless they asked for 0+
int idx = minIdx;
if (minIdx < 0) idx = 1;
while (idx < 1000)
{
String name = descriptor.GetFileName(idx);
if (!pkg.ContainPart(PackagingUriHelper.CreatePartName(name)))
{
return idx;
}
idx++;
}
}
catch (InvalidFormatException e)
{
// Give a general wrapped exception for the problem
throw new POIXMLException(e);
}
return -1;
}

/**
* Create a new child POIXMLDocumentPart
*
Expand Down
37 changes: 24 additions & 13 deletions ooxml/XSSF/UserModel/XSSFDataValidationConstraint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public XSSFDataValidationConstraint(int validationType, int operator1, String fo
public XSSFDataValidationConstraint(int validationType, int operator1, String formula1, String formula2)
: base()
{

//removes leading equals sign if present
Formula1 = (formula1);
Formula2 = (formula2);
this.validationType = validationType;
Expand All @@ -99,12 +99,10 @@ public XSSFDataValidationConstraint(int validationType, int operator1, String fo
// empirical testing shows Excel saves explicit lists surrounded by double quotes,
// range formula expressions can't start with quotes (I think - anyone have a creative counter example?)
if (ValidationType.LIST == validationType
&& formula1 != null
&& formula1.StartsWith(QUOTE)
&& formula1.EndsWith(QUOTE))
&& this.formula1 != null
&& IsQuoted(this.formula1))
{
String formulaWithoutQuotes = formula1.Substring(1, formula1.Length - 2);
explicitListOfValues = LIST_SPLIT_REGEX.Split(formulaWithoutQuotes);
explicitListOfValues = LIST_SPLIT_REGEX.Split(Unquote(this.formula1));
}
}

Expand Down Expand Up @@ -190,11 +188,29 @@ public int GetValidationType()
return validationType;
}

protected static String RemoveLeadingEquals(String formula1)
protected static string RemoveLeadingEquals(string formula1)
{
return IsFormulaEmpty(formula1) ? formula1 : formula1[0] == '=' ? formula1.Substring(1) : formula1;
}

private static bool IsQuoted(string s)
{
return s.StartsWith(QUOTE) && s.EndsWith(QUOTE);
}
private static String Unquote(string s)
{
// removes leading and trailing quotes from a quoted string
if (IsQuoted(s))
{
return s.Substring(1, s.Length - 2);
}
return s;
}
protected static bool IsFormulaEmpty(string formula1)
{
return formula1 == null || formula1.Trim().Length == 0;
}

public void Validate()
{
if (validationType == ValidationType.ANY)
Expand Down Expand Up @@ -230,12 +246,8 @@ public void Validate()
}
}

protected static bool IsFormulaEmpty(String formula1)
{
return formula1 == null || formula1.Trim().Length == 0;
}

public String PrettyPrint()
public string PrettyPrint()
{
StringBuilder builder = new StringBuilder();
ST_DataValidationType vt = XSSFDataValidation.validationTypeMappings[validationType];
Expand All @@ -245,7 +257,6 @@ public String PrettyPrint()
if (validationType != ValidationType.ANY)
{
if (validationType != ValidationType.LIST
&& validationType != ValidationType.ANY
&& validationType != ValidationType.FORMULA)
{
builder.Append(LIST_SEPARATOR).Append(ot).Append(", ");
Expand Down
57 changes: 52 additions & 5 deletions ooxml/XSSF/UserModel/XSSFName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ limitations under the License.
using NPOI.SS.UserModel;
using NPOI.OpenXmlFormats.Spreadsheet;
using System.Text.RegularExpressions;
using NPOI.SS;

namespace NPOI.XSSF.UserModel
{
Expand Down Expand Up @@ -352,7 +353,28 @@ public override bool Equals(Object o)
return _ctName.name == cf.GetCTName().name && _ctName.localSheetId == cf.GetCTName().localSheetId && _ctName.Value==cf.RefersToFormula ;
}

private static void ValidateName(String name)
/**
* https://support.office.com/en-us/article/Define-and-use-names-in-formulas-4D0F13AC-53B7-422E-AFD2-ABD7FF379C64#bmsyntax_rules_for_names
*
* Valid characters:
* First character: { letter | underscore | backslash }
* Remaining characters: { letter | number | period | underscore }
*
* Cell shorthand: cannot be { "C" | "c" | "R" | "r" }
*
* Cell references disallowed: cannot be a cell reference $A$1 or R1C1
*
* Spaces are not valid (follows from valid characters above)
*
* Name length: (XSSF-specific?) 255 characters maximum
*
* Case sensitivity: all names are case-insensitive
*
* Uniqueness: must be unique (for names with the same scope)
*
* @param name
*/
private static void ValidateName(string name)
{
/* equivalent to:
Pattern.compile(
Expand All @@ -367,26 +389,51 @@ thus we are stuck with Character.isLetter (for now).
{
throw new ArgumentException("Name cannot be blank");
}

if (name.Length > 255)
{
throw new ArgumentException("Invalid name: '" + name + "': cannot exceed 255 characters in length");
}
if (name.Equals("R", StringComparison.OrdinalIgnoreCase) || name.Equals("C", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Invalid name: '" + name + "': cannot be special shorthand R or C");
}
// is first character valid?
char c = name[0];
String allowedSymbols = "_";
string allowedSymbols = "_\\";
bool characterIsValid = (char.IsLetter(c) || allowedSymbols.IndexOf(c) != -1);
if (!characterIsValid)
{
throw new ArgumentException("Invalid name: '" + name + "': first character must be underscore or a letter");
}

// are all other characters valid?
allowedSymbols = "_\\"; //backslashes needed for unicode escape
allowedSymbols = "_.\\"; //backslashes needed for unicode escape
foreach (char ch in name.ToCharArray())
{
characterIsValid = (char.IsLetterOrDigit(ch) || allowedSymbols.IndexOf(ch) != -1);
if (!characterIsValid)
{
throw new ArgumentException("Invalid name: '" + name + "'");
throw new ArgumentException("Invalid name: '" + name + "': name must be letter, digit, period, or underscore");
}
}

// Is the name a valid $A$1 cell reference
// Because $, :, and ! are disallowed characters, A1-style references become just a letter-number combination
if (Regex.IsMatch(name, "[A-Za-z]+\\d+"))
{
string col = Regex.Replace(name, "\\d", "");
string row = Regex.Replace(name, "[A-Za-z]", "");
if (CellReference.CellReferenceIsWithinRange(col, row, SpreadsheetVersion.EXCEL97))
{
throw new ArgumentException("Invalid name: '" + name + "': cannot be $A$1-style cell reference");
}
}

// Is the name a valid R1C1 cell reference?
if (Regex.IsMatch(name, "[Rr]\\d+[Cc]\\d+"))
{
throw new ArgumentException("Invalid name: '" + name + "': cannot be R1C1-style cell reference");
}
}
}
}
7 changes: 4 additions & 3 deletions ooxml/XSSF/UserModel/XSSFSheet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1743,13 +1743,14 @@ public IDrawing CreateDrawingPatriarch()
return GetDrawingPatriarch();
}

//drawingNumber = #drawings.Count + 1
int DrawingNumber = GetPackagePart()
// Default drawingNumber = #drawings.Count + 1
int drawingNumber = GetPackagePart()
.Package.GetPartsByContentType(XSSFRelation.DRAWINGS.ContentType).Count + 1;
drawingNumber = GetNextPartNumber(XSSFRelation.DRAWINGS, drawingNumber);
RelationPart rp = CreateRelationship(
XSSFRelation.DRAWINGS,
XSSFFactory.GetInstance(),
DrawingNumber,
drawingNumber,
false);
XSSFDrawing drawing = rp.DocumentPart as XSSFDrawing;
string relId = rp.Relationship.Id;
Expand Down
10 changes: 6 additions & 4 deletions testcases/main/POIFS/FileSystem/TestNPOIFSFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,16 @@ public static NPOIFSFileSystem WriteOutAndReadBack(NPOIFSFileSystem original)
protected static NPOIFSFileSystem WriteOutFileAndReadBack(NPOIFSFileSystem original)
{
FileInfo file = TempFile.CreateTempFile("TestPOIFS", ".ole2");
using (FileStream fout = file.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite))
try
{
original.WriteFileSystem(fout);
using(FileStream fout = file.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
original.WriteFileSystem(fout);
}
}
finally
catch
{
fout.Close();
// avoid throw exception
}
original.Close();
return new NPOIFSFileSystem(file, false);
Expand Down
Loading